[Screen Ruler] optimize d3d device & continuous capture mode (#20198)

* [Screen Ruler] Exclude overlay window from capture

* [Screen ruler] Sync OverlayUI threads creation and don't recreate d3d device
This commit is contained in:
Andrey Nekrasov
2022-08-31 17:43:32 +03:00
committed by GitHub
parent 76ebed0897
commit c7d1465946
13 changed files with 207 additions and 103 deletions

View File

@@ -346,6 +346,7 @@ CSIDL
csignal
cso
CSRW
cstddef
cstdint
cstdlib
cstring
@@ -489,6 +490,7 @@ dreamsofameaningfullife
drivedetectionwarning
dshow
dst
DState
DTo
dutil
DVASPECT
@@ -579,6 +581,7 @@ EWXREBOOT
EWXSHUTDOWN
examplehandler
examplepowertoy
EXCLUDEFROMCAPTURE
exdisp
Executables
executionpolicy
@@ -1628,6 +1631,7 @@ ptd
PTOKEN
PToy
ptr
ptrdiff
ptstr
PVOID
pwa
@@ -2273,6 +2277,7 @@ wcscpy
wcslen
wcsncmp
wcsnicmp
WDA
wdp
wdupenv
weakme

View File

@@ -28,8 +28,6 @@ D2DState::D2DState(const HWND overlayWindow, std::vector<D2D1::ColorF> solidBrus
winrt::check_bool(GetClientRect(overlayWindow, &clientRect));
winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &d2dFactory));
winrt::check_hresult(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), writeFactory.put_unknown()));
// We should always use DPIAware::DEFAULT_DPI, since it's the correct thing to do in DPI-Aware mode
auto renderTargetProperties = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
@@ -49,6 +47,7 @@ D2DState::D2DState(const HWND overlayWindow, std::vector<D2D1::ColorF> solidBrus
DPIAware::GetScreenDPIForWindow(overlayWindow, dpi);
dpiScale = dpi / static_cast<float>(DPIAware::DEFAULT_DPI);
winrt::check_hresult(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), writeFactory.put_unknown()));
winrt::check_hresult(writeFactory->CreateTextFormat(L"Segoe UI Variable Text",
nullptr,
DWRITE_FONT_WEIGHT_NORMAL,

View File

@@ -10,28 +10,10 @@ inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, co
{
using namespace consts;
long xOffset = 0;
long yOffset = 0;
// For continuous capture, we'll be a bit off center from the cursor so the cross we draw won't interfere with the measurement.
if constexpr (ContinuousCapture)
{
if constexpr (IsX)
{
xOffset = Increment ? CURSOR_OFFSET_AMOUNT_X : -CURSOR_OFFSET_AMOUNT_X;
yOffset = 1;
}
else
{
xOffset = 1;
yOffset = Increment ? CURSOR_OFFSET_AMOUNT_Y : -CURSOR_OFFSET_AMOUNT_Y;
}
}
const size_t maxDim = IsX ? texture.width : texture.height;
long x = std::clamp<long>(centerPoint.x + xOffset, 1, static_cast<long>(texture.width - 2));
long y = std::clamp<long>(centerPoint.y + yOffset, 1, static_cast<long>(texture.height - 2));
long x = std::clamp<long>(centerPoint.x, 1, static_cast<long>(texture.width - 2));
long y = std::clamp<long>(centerPoint.y, 1, static_cast<long>(texture.height - 2));
const uint32_t startPixel = texture.GetPixel(x, y);
while (true)

View File

@@ -217,7 +217,7 @@ void DrawMeasureToolTick(const CommonState& commonState,
D2D_POINT_2F hLineEnd{ .x = hLineStart.x + hMeasure, .y = hLineStart.y };
d2dState.rt->DrawLine(hLineStart, hLineEnd, d2dState.solidBrushes[Brush::line].get());
if (drawFeetOnCross && !continuousCapture)
if (drawFeetOnCross)
{
// To fill all pixels which are close, we call DrawLine with end point one pixel too far, since
// it doesn't get filled, i.e. end point of the range is excluded. However, we want to draw cross
@@ -236,7 +236,7 @@ void DrawMeasureToolTick(const CommonState& commonState,
D2D_POINT_2F vLineEnd{ .x = vLineStart.x, .y = vLineStart.y + vMeasure };
d2dState.rt->DrawLine(vLineStart, vLineEnd, d2dState.solidBrushes[Brush::line].get());
if (drawFeetOnCross && !continuousCapture)
if (drawFeetOnCross)
{
vLineEnd.y -= 1.f;
auto [top_start, top_end] = ComputeCrossFeetLine(vLineStart, true);

View File

@@ -59,6 +59,7 @@ HWND CreateOverlayUIWindow(const CommonState& commonState,
};
winrt::check_bool(window);
ShowWindow(window, SW_SHOWNORMAL);
SetWindowDisplayAffinity(window, WDA_EXCLUDEFROMCAPTURE);
#if !defined(DEBUG_OVERLAY)
SetWindowPos(window, HWND_TOPMOST, {}, {}, {}, {}, SWP_NOMOVE | SWP_NOSIZE);
#else
@@ -136,13 +137,8 @@ void OverlayUIState::RunUILoop()
else
_d2dState.rt->Clear();
{
// TODO: use latch to wait until all threads are created their corresponding d2d textures
// in the non-continuous mode
// std::lock_guard guard{ gpuAccessLock };
_d2dState.rt->EndDraw();
}
}
run_message_loop(true, 1);
}
@@ -182,6 +178,7 @@ template<typename ToolT, typename TickFuncT>
inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& toolState,
TickFuncT tickFunc,
CommonState& commonState,
Latch& creationLatch,
const wchar_t* toolWindowClassName,
void* windowParam,
const MonitorInfo& monitor,
@@ -199,6 +196,9 @@ inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& too
auto* state = uiState.get();
uiCreatedEvent.SetEvent();
// Wait until all OverlayUI threads created their corresponding d2d devices
creationLatch.arrive_and_wait();
state->RunUILoop();
commonState.closeOnOtherMonitors = true;
@@ -211,12 +211,14 @@ inline std::unique_ptr<OverlayUIState> OverlayUIState::CreateInternal(ToolT& too
}
std::unique_ptr<OverlayUIState> OverlayUIState::Create(Serialized<MeasureToolState>& toolState,
Latch& creationLatch,
CommonState& commonState,
const MonitorInfo& monitor)
{
return OverlayUIState::CreateInternal(toolState,
DrawMeasureToolTick,
commonState,
creationLatch,
NonLocalizable::MeasureToolOverlayWindowName,
&toolState,
monitor,
@@ -224,12 +226,14 @@ std::unique_ptr<OverlayUIState> OverlayUIState::Create(Serialized<MeasureToolSta
}
std::unique_ptr<OverlayUIState> OverlayUIState::Create(BoundsToolState& toolState,
Latch& creationLatch,
CommonState& commonState,
const MonitorInfo& monitor)
{
return OverlayUIState::CreateInternal(toolState,
DrawBoundsToolTick,
commonState,
creationLatch,
NonLocalizable::BoundsToolOverlayWindowName,
&toolState,
monitor,

View File

@@ -1,6 +1,7 @@
#pragma once
#include "D2DState.h"
#include "latch.h"
#include "ToolState.h"
#include <common/display/monitors.h>
@@ -27,6 +28,7 @@ class OverlayUIState final
static std::unique_ptr<OverlayUIState> CreateInternal(ToolT& toolState,
TickFuncT tickFunc,
CommonState& commonState,
Latch& creationLatch,
const wchar_t* toolWindowClassName,
void* windowParam,
const MonitorInfo& monitor,
@@ -37,9 +39,11 @@ public:
~OverlayUIState();
static std::unique_ptr<OverlayUIState> Create(BoundsToolState& toolState,
Latch& creationLatch,
CommonState& commonState,
const MonitorInfo& monitor);
static std::unique_ptr<OverlayUIState> Create(Serialized<MeasureToolState>& toolState,
Latch& creationLatch,
CommonState& commonState,
const MonitorInfo& monitor);
inline HWND overlayWindowHandle() const

View File

@@ -79,11 +79,17 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
#if defined(DEBUG_PRIMARY_MONITOR_ONLY)
const auto& monitorInfo = MonitorInfo::GetPrimaryMonitor();
Latch createOverlayUILatch{ 1 };
#else
for (const auto& monitorInfo : MonitorInfo::GetMonitors(true))
const auto monitors = MonitorInfo::GetMonitors(true);
Latch createOverlayUILatch{ static_cast<ptrdiff_t>(monitors.size()) };
for (const auto& monitorInfo : monitors)
#endif
{
auto overlayUI = OverlayUIState::Create(_boundsToolState, _commonState, monitorInfo);
auto overlayUI = OverlayUIState::Create(_boundsToolState,
createOverlayUILatch,
_commonState,
monitorInfo);
#if !defined(DEBUG_PRIMARY_MONITOR_ONLY)
if (!overlayUI)
continue;
@@ -110,22 +116,37 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
});
#if defined(DEBUG_PRIMARY_MONITOR_ONLY)
const auto& monitorInfo = MonitorInfo::GetPrimaryMonitor();
std::vector<MonitorInfo> monitors = { MonitorInfo::GetPrimaryMonitor() };
const auto& monitorInfo = monitors[0];
Latch createOverlayUILatch{ 1 };
#else
for (const auto& monitorInfo : MonitorInfo::GetMonitors(true))
const auto monitors = MonitorInfo::GetMonitors(true);
Latch createOverlayUILatch{ static_cast<ptrdiff_t>(monitors.size()) };
for (const auto& monitorInfo : monitors)
#endif
{
auto overlayUI = OverlayUIState::Create(_measureToolState, _commonState, monitorInfo);
auto overlayUI = OverlayUIState::Create(_measureToolState,
createOverlayUILatch,
_commonState,
monitorInfo);
#if !defined(DEBUG_PRIMARY_MONITOR_ONLY)
if (!overlayUI)
continue;
return;
#endif
_screenCaptureThreads.emplace_back(StartCapturingThread(_commonState,
_measureToolState,
overlayUI->overlayWindowHandle(),
monitorInfo));
_overlayUIStates.push_back(std::move(overlayUI));
}
for (size_t i = 0; i < monitors.size(); ++i)
{
auto thread = StartCapturingThread(
&_d3dState,
_commonState,
_measureToolState,
_overlayUIStates[i]->overlayWindowHandle(),
monitors[i]);
_screenCaptureThreads.emplace_back(std::move(thread));
}
Trace::MeasureToolActivated();
}

View File

@@ -6,6 +6,7 @@
#include "Settings.h"
#include <common/utils/serialized.h>
#include "ScreenCapturing.h"
namespace winrt::PowerToys::MeasureToolCore::implementation
{
@@ -21,6 +22,8 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
float GetDPIScaleForWindow(uint64_t windowHandle);
void MouseCaptureThread();
D3DState _d3dState;
wil::shared_event _stopMouseCaptureThreadSignal;
std::thread _mouseCaptureThread;
std::vector<std::thread> _screenCaptureThreads;

View File

@@ -86,6 +86,7 @@
<ClInclude Include="BGRATextureView.h" />
<ClInclude Include="EdgeDetection.h" />
<ClInclude Include="ToolState.h" />
<ClInclude Include="latch.h" />
<ClInclude Include="OverlayUI.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="ScreenCapturing.h" />

View File

@@ -10,9 +10,45 @@
//#define DEBUG_EDGES
D3DState::D3DState()
{
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#ifndef NDEBUG
flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
HRESULT hr =
D3D11CreateDevice(nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
flags,
nullptr,
0,
D3D11_SDK_VERSION,
d3dDevice.put(),
nullptr,
nullptr);
if (hr == DXGI_ERROR_UNSUPPORTED)
{
hr = D3D11CreateDevice(nullptr,
D3D_DRIVER_TYPE_WARP,
nullptr,
flags,
nullptr,
0,
D3D11_SDK_VERSION,
d3dDevice.put(),
nullptr,
nullptr);
}
winrt::check_hresult(hr);
dxgiDevice = d3dDevice.as<IDXGIDevice>();
winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), d3dDeviceInspectable.put()));
}
class D3DCaptureState final
{
winrt::com_ptr<ID3D11Device> d3dDevice;
D3DState* d3dState = nullptr;
winrt::IDirect3DDevice device;
winrt::com_ptr<IDXGISwapChain1> swapChain;
winrt::com_ptr<ID3D11DeviceContext> context;
@@ -26,8 +62,7 @@ class D3DCaptureState final
Box monitorArea;
bool captureOutsideOfMonitor = false;
D3DCaptureState(winrt::com_ptr<ID3D11Device> d3dDevice,
winrt::IDirect3DDevice _device,
D3DCaptureState(D3DState* d3dState,
winrt::com_ptr<IDXGISwapChain1> _swapChain,
winrt::com_ptr<ID3D11DeviceContext> _context,
const winrt::GraphicsCaptureItem& item,
@@ -44,7 +79,8 @@ class D3DCaptureState final
std::mutex destructorMutex;
public:
static std::unique_ptr<D3DCaptureState> Create(winrt::GraphicsCaptureItem item,
static std::unique_ptr<D3DCaptureState> Create(D3DState* d3dState,
winrt::GraphicsCaptureItem item,
const winrt::DirectXPixelFormat pixelFormat,
Box monitorSize,
const bool captureOutsideOfMonitor);
@@ -57,21 +93,20 @@ public:
void StopCapture();
};
D3DCaptureState::D3DCaptureState(winrt::com_ptr<ID3D11Device> _d3dDevice,
winrt::IDirect3DDevice _device,
D3DCaptureState::D3DCaptureState(D3DState* _d3dState,
winrt::com_ptr<IDXGISwapChain1> _swapChain,
winrt::com_ptr<ID3D11DeviceContext> _context,
const winrt::GraphicsCaptureItem& item,
winrt::DirectXPixelFormat _pixelFormat,
Box _monitorArea,
const bool _captureOutsideOfMonitor) :
d3dDevice{ std::move(_d3dDevice) },
device{ std::move(_device) },
d3dState{ _d3dState },
device{ _d3dState->d3dDeviceInspectable.as<winrt::IDirect3DDevice>() },
swapChain{ std::move(_swapChain) },
context{ std::move(_context) },
frameSize{ item.Size() },
pixelFormat{ std::move(_pixelFormat) },
framePool{ winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(device, pixelFormat, 2, item.Size()) },
framePool{ winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(device, pixelFormat, 1, item.Size()) },
session{ framePool.CreateCaptureSession(item) },
monitorArea{ _monitorArea },
captureOutsideOfMonitor{ _captureOutsideOfMonitor }
@@ -89,7 +124,7 @@ winrt::com_ptr<ID3D11Texture2D> D3DCaptureState::CopyFrameToCPU(const winrt::com
desc.BindFlags = 0;
winrt::com_ptr<ID3D11Texture2D> cpuTexture;
winrt::check_hresult(d3dDevice->CreateTexture2D(&desc, nullptr, cpuTexture.put()));
winrt::check_hresult(d3dState->d3dDevice->CreateTexture2D(&desc, nullptr, cpuTexture.put()));
context->CopyResource(cpuTexture.get(), frameTexture.get());
return cpuTexture;
@@ -152,49 +187,14 @@ void D3DCaptureState::OnFrameArrived(const winrt::Direct3D11CaptureFramePool& se
}
}
std::unique_ptr<D3DCaptureState> D3DCaptureState::Create(winrt::GraphicsCaptureItem item,
std::unique_ptr<D3DCaptureState> D3DCaptureState::Create(D3DState* d3dState,
winrt::GraphicsCaptureItem item,
const winrt::DirectXPixelFormat pixelFormat,
Box monitorArea,
const bool captureOutsideOfMonitor)
{
std::lock_guard guard{ gpuAccessLock };
winrt::com_ptr<ID3D11Device> d3dDevice;
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#ifndef NDEBUG
flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
HRESULT hr =
D3D11CreateDevice(nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
flags,
nullptr,
0,
D3D11_SDK_VERSION,
d3dDevice.put(),
nullptr,
nullptr);
if (hr == DXGI_ERROR_UNSUPPORTED)
{
hr = D3D11CreateDevice(nullptr,
D3D_DRIVER_TYPE_WARP,
nullptr,
flags,
nullptr,
0,
D3D11_SDK_VERSION,
d3dDevice.put(),
nullptr,
nullptr);
}
winrt::check_hresult(hr);
auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
winrt::com_ptr<IInspectable> d3dDeviceInspectable;
winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), d3dDeviceInspectable.put()));
const DXGI_SWAP_CHAIN_DESC1 desc = {
.Width = static_cast<uint32_t>(item.Size().Width),
.Height = static_cast<uint32_t>(item.Size().Height),
@@ -207,20 +207,21 @@ std::unique_ptr<D3DCaptureState> D3DCaptureState::Create(winrt::GraphicsCaptureI
.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED,
};
winrt::com_ptr<IDXGIAdapter> adapter;
winrt::check_hresult(dxgiDevice->GetParent(winrt::guid_of<IDXGIAdapter>(), adapter.put_void()));
winrt::check_hresult(d3dState->dxgiDevice->GetParent(winrt::guid_of<IDXGIAdapter>(), adapter.put_void()));
winrt::com_ptr<IDXGIFactory2> factory;
winrt::check_hresult(adapter->GetParent(winrt::guid_of<IDXGIFactory2>(), factory.put_void()));
winrt::com_ptr<IDXGISwapChain1> swapChain;
winrt::check_hresult(factory->CreateSwapChainForComposition(d3dDevice.get(), &desc, nullptr, swapChain.put()));
winrt::check_hresult(factory->CreateSwapChainForComposition(d3dState->d3dDevice.get(), &desc, nullptr, swapChain.put()));
winrt::com_ptr<ID3D11DeviceContext> context;
d3dDevice->GetImmediateContext(context.put());
d3dState->d3dDevice->GetImmediateContext(context.put());
winrt::check_bool(context);
auto contextMultithread = context.as<ID3D11Multithread>();
contextMultithread->SetMultithreadProtected(true);
// We must create the object in a heap, since we need to pin it in memory to receive callbacks
auto statePtr = new D3DCaptureState{ d3dDevice,
d3dDeviceInspectable.as<winrt::IDirect3DDevice>(),
auto statePtr = new D3DCaptureState{ d3dState,
std::move(swapChain),
std::move(context),
item,
@@ -236,7 +237,6 @@ D3DCaptureState::~D3DCaptureState()
std::unique_lock callbackLock{ destructorMutex };
StopCapture();
framePool.Close();
device.Close();
}
void D3DCaptureState::StartSessionInPreferredMode()
@@ -332,12 +332,13 @@ void UpdateCaptureState(const CommonState& commonState,
});
}
std::thread StartCapturingThread(const CommonState& commonState,
std::thread StartCapturingThread(D3DState* d3dState,
const CommonState& commonState,
Serialized<MeasureToolState>& state,
HWND window,
MonitorInfo targetMonitor)
MonitorInfo monitor)
{
return SpawnLoggedThread(L"Screen Capture thread", [&state, &commonState, targetMonitor, window] {
return SpawnLoggedThread(L"Screen Capture thread", [&state, &commonState, monitor, window, d3dState] {
auto captureInterop = winrt::get_activation_factory<
winrt::GraphicsCaptureItem,
IGraphicsCaptureItemInterop>();
@@ -345,7 +346,7 @@ std::thread StartCapturingThread(const CommonState& commonState,
winrt::GraphicsCaptureItem item = nullptr;
winrt::check_hresult(captureInterop->CreateForMonitor(
targetMonitor.GetHandle(),
monitor.GetHandle(),
winrt::guid_of<winrt::GraphicsCaptureItem>(),
winrt::put_abi(item)));
@@ -354,8 +355,9 @@ std::thread StartCapturingThread(const CommonState& commonState,
continuousCapture = state.global.continuousCapture;
});
const auto monitorArea = targetMonitor.GetScreenSize(true);
auto captureState = D3DCaptureState::Create(item,
const auto monitorArea = monitor.GetScreenSize(true);
auto captureState = D3DCaptureState::Create(d3dState,
item,
winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized,
monitorArea,
!continuousCapture);

View File

@@ -4,7 +4,17 @@
#include <common/utils/serialized.h>
std::thread StartCapturingThread(const CommonState& commonState,
struct D3DState
{
winrt::com_ptr<ID3D11Device> d3dDevice;
winrt::com_ptr<IDXGIDevice> dxgiDevice;
winrt::com_ptr<IInspectable> d3dDeviceInspectable;
D3DState();
};
std::thread StartCapturingThread(D3DState* d3dState,
const CommonState& commonState,
Serialized<MeasureToolState>& state,
HWND targetWindow,
MonitorInfo targetMonitor);

View File

@@ -0,0 +1,73 @@
#pragma once
#if __has_include(<latch>)
#include <latch>
using Latch = std::latch;
#else
#include <atomic>
#include <cstddef>
#include <limits.h>
class Latch
{
public:
[[nodiscard]] static constexpr ptrdiff_t(max)() noexcept
{
return (1ULL << (sizeof(ptrdiff_t) * CHAR_BIT - 1)) - 1;
}
constexpr explicit Latch(const std::ptrdiff_t _Expected) noexcept :
_Counter{ _Expected }
{
}
Latch(const Latch&) = delete;
Latch& operator=(const Latch&) = delete;
void count_down(const ptrdiff_t _Update = 1) noexcept
{
const ptrdiff_t _Current = _Counter.fetch_sub(_Update) - _Update;
if (_Current == 0)
{
_Counter.notify_all();
}
}
[[nodiscard]] bool try_wait() const noexcept
{
return _Counter.load() == 0;
}
void wait() const noexcept
{
for (;;)
{
const ptrdiff_t _Current = _Counter.load();
if (_Current == 0)
{
return;
}
else
_Counter.wait(_Current, std::memory_order_relaxed);
}
}
void arrive_and_wait(const ptrdiff_t _Update = 1) noexcept
{
const ptrdiff_t _Current = _Counter.fetch_sub(_Update) - _Update;
if (_Current == 0)
{
_Counter.notify_all();
}
else
{
_Counter.wait(_Current, std::memory_order_relaxed);
wait();
}
}
private:
std::atomic<std::ptrdiff_t> _Counter;
};
#endif

View File

@@ -177,7 +177,7 @@
<value>Draw feet on cross</value>
</data>
<data name="MeasureTool_DrawFeetOnCross.Description" xml:space="preserve">
<value>Adds feet to the end of cross lines. Always off for continuous capture.</value>
<value>Adds feet to the end of cross lines.</value>
</data>
<data name="MeasureTool_EnableMeasureTool.Header" xml:space="preserve">
<value>Enable Screen Ruler</value>
@@ -2374,7 +2374,7 @@ Activate by holding the key for the character you want to add an accent to, then
<value>Learn more about conflicting activation commands</value>
</data>
<data name="MeasureTool_ContinuousCapture_Information.Title" xml:space="preserve">
<value>The continuous capture mode will consume more resources when in use. In addition, the UI will be slightly offset from the pointer position.</value>
<comment>pointer as in mouse pointer. resources refer to things like CPU, GPU, RAM</comment>
<value>The continuous capture mode will consume more resources when in use.</value>
<comment>pointer as in mouse pointer. Resources refer to things like CPU, GPU, RAM</comment>
</data>
</root>