diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 4b08de51274..97a6ad2879a 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -346,7 +346,6 @@ CSIDL
csignal
cso
CSRW
-cstddef
cstdint
cstdlib
cstring
@@ -402,6 +401,7 @@ DBLEPSILON
DCapture
DCBA
DCOM
+dcommon
dcomp
dcompi
DComposition
@@ -490,7 +490,6 @@ dreamsofameaningfullife
drivedetectionwarning
dshow
dst
-DState
DTo
dutil
DVASPECT
@@ -522,6 +521,7 @@ DWORDLONG
dworigin
dwrite
dxgi
+dxgidebug
dxgiformat
dxguid
ecount
@@ -811,6 +811,7 @@ ICapture
icase
ICEBLUE
IClass
+IClosable
ICollection
IColor
ICommand
@@ -881,7 +882,6 @@ IMAGERESIZERCONTEXTMENU
IMAGERESIZEREXT
imageresizerinput
imageresizersettings
-TEXTEXTRACTOR
imagingdevices
IMain
IMarkdown
@@ -1278,6 +1278,7 @@ Moq
MOUSEACTIVATE
MOUSEHWHEEL
MOUSEINPUT
+MOUSELEAVE
MOUSEMOVE
MOUSEWHEEL
MOVESIZEEND
@@ -1632,7 +1633,6 @@ ptd
PTOKEN
PToy
ptr
-ptrdiff
ptstr
PVOID
pwa
@@ -1746,6 +1746,7 @@ RIGHTSCROLLBAR
riid
riverar
RKey
+RLO
RMENU
RNumber
roadmap
@@ -2063,6 +2064,7 @@ testhost
testprocess
TEXCOORD
textblock
+TEXTEXTRACTOR
TEXTINCLUDE
THH
THICKFRAME
@@ -2272,7 +2274,6 @@ wchar
WClass
wcout
wcscat
-wcschr
wcscmp
wcscpy
wcslen
diff --git a/Cpp.Build.props b/Cpp.Build.props
index da85f4fb56a..9b56bed178e 100644
--- a/Cpp.Build.props
+++ b/Cpp.Build.props
@@ -32,6 +32,7 @@
+ x64
false
$(MSBuildThisFileFullPath)\..\deps\;$(ExternalIncludePath)
diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs
index 05c88b72ffc..3e7a208f7bb 100644
--- a/installer/PowerToysSetup/Product.wxs
+++ b/installer/PowerToysSetup/Product.wxs
@@ -119,7 +119,7 @@
-
+
diff --git a/src/common/utils/UnhandledExceptionHandler.h b/src/common/utils/UnhandledExceptionHandler.h
index 037bcc8c92a..a3a69b29328 100644
--- a/src/common/utils/UnhandledExceptionHandler.h
+++ b/src/common/utils/UnhandledExceptionHandler.h
@@ -156,7 +156,7 @@ inline void LogStackTrace()
Logger::error(L"Failed to capture context. {}", get_last_error_or_default(GetLastError()));
return;
}
-
+
STACKFRAME64 stack;
memset(&stack, 0, sizeof(STACKFRAME64));
@@ -238,14 +238,14 @@ inline LONG WINAPI UnhandledExceptionHandler(PEXCEPTION_POINTERS info)
}
/* Handler to trap abort() calls */
-inline void AbortHandler(int signal_number)
+inline void AbortHandler(int /*signal_number*/)
{
Logger::error("--- ABORT");
try
{
LogStackTrace();
}
- catch(...)
+ catch (...)
{
Logger::error("Failed to log stack trace on abort");
Logger::flush();
@@ -271,9 +271,9 @@ inline void InitUnhandledExceptionHandler(void)
// Global handler for unhandled exceptions
SetUnhandledExceptionFilter(UnhandledExceptionHandler);
// Handler for abort()
- signal(SIGABRT, &AbortHandler);
+ signal(SIGABRT, &AbortHandler);
}
- catch(...)
+ catch (...)
{
Logger::error("Failed to init global unhandled exception handler");
}
diff --git a/src/modules/MeasureTool/MeasureToolCore/BoundsToolOverlayUI.cpp b/src/modules/MeasureTool/MeasureToolCore/BoundsToolOverlayUI.cpp
index d061df5db3a..b5f21f40743 100644
--- a/src/modules/MeasureTool/MeasureToolCore/BoundsToolOverlayUI.cpp
+++ b/src/modules/MeasureTool/MeasureToolCore/BoundsToolOverlayUI.cpp
@@ -30,7 +30,7 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
auto toolState = GetWindowParam(window);
if (!toolState)
break;
- const POINT cursorPos = convert::FromSystemToRelativeForDirect2D(window, toolState->commonState->cursorPosSystemSpace);
+ const POINT cursorPos = convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace);
D2D_POINT_2F newRegionStart = { .x = static_cast(cursorPos.x), .y = static_cast(cursorPos.y) };
toolState->perScreen[window].currentRegionStart = newRegionStart;
@@ -38,6 +38,8 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
}
case WM_CURSOR_LEFT_MONITOR:
{
+ for (; ShowCursor(true) < 0;)
+ ;
auto toolState = GetWindowParam(window);
if (!toolState)
break;
@@ -59,12 +61,12 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x8000; shiftPress)
{
- const auto cursorPos = convert::FromSystemToRelativeForDirect2D(window, toolState->commonState->cursorPosSystemSpace);
+ const auto cursorPos = convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace);
D2D1_RECT_F rect;
std::tie(rect.left, rect.right) = std::minmax(static_cast(cursorPos.x), toolState->perScreen[window].currentRegionStart->x);
std::tie(rect.top, rect.bottom) = std::minmax(static_cast(cursorPos.y), toolState->perScreen[window].currentRegionStart->y);
- toolState->perScreen[window].measurements.push_back(rect);
+ toolState->perScreen[window].measurements.push_back(Measurement{ rect });
}
toolState->perScreen[window].currentRegionStart = std::nullopt;
@@ -97,45 +99,42 @@ LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPA
namespace
{
- void DrawMeasurement(const D2D1_RECT_F rect,
+ void DrawMeasurement(const Measurement& measurement,
const bool alignTextBoxToCenter,
const CommonState& commonState,
HWND window,
- const D2DState& d2dState)
+ const D2DState& d2dState,
+ float mouseX,
+ float mouseY)
{
const bool screenQuadrantAware = !alignTextBoxToCenter;
- const auto prevMode = d2dState.rt->GetAntialiasMode();
- d2dState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
- d2dState.rt->DrawRectangle(rect, d2dState.solidBrushes[Brush::line].get());
- d2dState.rt->SetAntialiasMode(prevMode);
+ d2dState.ToggleAliasedLinesMode(true);
+ d2dState.dxgiWindowState.rt->DrawRectangle(measurement.rect, d2dState.solidBrushes[Brush::line].get());
+ d2dState.ToggleAliasedLinesMode(false);
OverlayBoxText text;
- const auto width = std::abs(rect.right - rect.left + 1);
- const auto height = std::abs(rect.top - rect.bottom + 1);
- const uint32_t textLen = swprintf_s(text.buffer.data(),
- text.buffer.size(),
- L"%.0f × %.0f",
- width,
- height);
- std::optional crossSymbolPos = wcschr(text.buffer.data(), L' ') - text.buffer.data() + 1;
+ const auto [crossSymbolPos, measureStringBufLen] =
+ measurement.Print(text.buffer.data(),
+ text.buffer.size(),
+ true,
+ true,
+ commonState.units);
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
v = text;
});
- float cornerX = rect.right;
- float cornerY = rect.bottom;
if (alignTextBoxToCenter)
{
- cornerX = rect.left + width / 2;
- cornerY = rect.top + height / 2;
+ mouseX = measurement.rect.left + measurement.Width(Measurement::Unit::Pixel) / 2;
+ mouseY = measurement.rect.top + measurement.Height(Measurement::Unit::Pixel) / 2;
}
d2dState.DrawTextBox(text.buffer.data(),
- textLen,
+ measureStringBufLen,
crossSymbolPos,
- cornerX,
- cornerY,
+ mouseX,
+ mouseY,
screenQuadrantAware,
window);
}
@@ -150,20 +149,21 @@ void DrawBoundsToolTick(const CommonState& commonState,
if (it == end(toolState.perScreen))
return;
- d2dState.rt->Clear();
+ d2dState.dxgiWindowState.rt->Clear();
const auto& perScreen = it->second;
for (const auto& measure : perScreen.measurements)
- DrawMeasurement(measure, true, commonState, window, d2dState);
+ DrawMeasurement(measure, true, commonState, window, d2dState, measure.rect.right, measure.rect.bottom);
if (!perScreen.currentRegionStart.has_value())
return;
- const auto cursorPos = convert::FromSystemToRelativeForDirect2D(window, commonState.cursorPosSystemSpace);
+ const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
- const D2D1_RECT_F rect{ .left = perScreen.currentRegionStart->x,
- .top = perScreen.currentRegionStart->y,
- .right = static_cast(cursorPos.x),
- .bottom = static_cast(cursorPos.y) };
- DrawMeasurement(rect, false, commonState, window, d2dState);
+ D2D1_RECT_F rect;
+ const float cursorX = static_cast(cursorPos.x);
+ const float cursorY = static_cast(cursorPos.y);
+ std::tie(rect.left, rect.right) = std::minmax(cursorX, perScreen.currentRegionStart->x);
+ std::tie(rect.top, rect.bottom) = std::minmax(cursorY, perScreen.currentRegionStart->y);
+ DrawMeasurement(Measurement{ rect }, false, commonState, window, d2dState, cursorX, cursorY);
}
diff --git a/src/modules/MeasureTool/MeasureToolCore/CoordinateSystemConversion.h b/src/modules/MeasureTool/MeasureToolCore/CoordinateSystemConversion.h
index a140809bf79..152b627a817 100644
--- a/src/modules/MeasureTool/MeasureToolCore/CoordinateSystemConversion.h
+++ b/src/modules/MeasureTool/MeasureToolCore/CoordinateSystemConversion.h
@@ -6,21 +6,9 @@
namespace convert
{
// Converts a given point from multi-monitor coordinate system to the one relative to HWND
- inline POINT FromSystemToRelative(HWND window, POINT p)
+ inline POINT FromSystemToWindow(HWND window, POINT p)
{
ScreenToClient(window, &p);
return p;
}
-
- // Converts a given point from multi-monitor coordinate system to the one relative to HWND and also ready
- // to be used in Direct2D calls with AA mode set to aliased
- inline POINT FromSystemToRelativeForDirect2D(HWND window, POINT p)
- {
- ScreenToClient(window, &p);
- // Submitting DrawLine calls to Direct2D with thickness == 1.f and AA mode set to aliased causes
- // them to be drawn offset by [1,1] toward upper-left corner, so we must to compensate for that.
- ++p.x;
- ++p.y;
- return p;
- }
}
diff --git a/src/modules/MeasureTool/MeasureToolCore/D2DState.cpp b/src/modules/MeasureTool/MeasureToolCore/D2DState.cpp
index cd8bb24e358..62ffc430207 100644
--- a/src/modules/MeasureTool/MeasureToolCore/D2DState.cpp
+++ b/src/modules/MeasureTool/MeasureToolCore/D2DState.cpp
@@ -2,6 +2,7 @@
#include "constants.h"
#include "D2DState.h"
+#include "DxgiAPI.h"
#include
#include
@@ -19,43 +20,28 @@ namespace
}
}
-D2DState::D2DState(const HWND overlayWindow, std::vector solidBrushesColors)
+D2DState::D2DState(const DxgiAPI* dxgi,
+ HWND window,
+ std::vector solidBrushesColors)
{
- std::lock_guard guard{ gpuAccessLock };
-
- RECT clientRect = {};
-
- winrt::check_bool(GetClientRect(overlayWindow, &clientRect));
- winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &d2dFactory));
-
- // 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,
- D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
- DPIAware::DEFAULT_DPI,
- DPIAware::DEFAULT_DPI,
- D2D1_RENDER_TARGET_USAGE_NONE,
- D2D1_FEATURE_LEVEL_DEFAULT);
-
- auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
- auto hwndRenderTargetProperties = D2D1::HwndRenderTargetProperties(overlayWindow, renderTargetSize);
-
- winrt::check_hresult(d2dFactory->CreateHwndRenderTarget(renderTargetProperties, hwndRenderTargetProperties, &rt));
- winrt::check_hresult(rt->CreateCompatibleRenderTarget(&bitmapRt));
+ dxgiAPI = dxgi;
unsigned dpi = DPIAware::DEFAULT_DPI;
- DPIAware::GetScreenDPIForWindow(overlayWindow, dpi);
+ DPIAware::GetScreenDPIForWindow(window, dpi);
dpiScale = dpi / static_cast(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,
- DWRITE_FONT_STYLE_NORMAL,
- DWRITE_FONT_STRETCH_NORMAL,
- consts::FONT_SIZE * dpiScale,
- L"en-US",
- &textFormat));
+ dxgiWindowState = dxgiAPI->CreateD2D1RenderTarget(window);
+
+ winrt::check_hresult(dxgiWindowState.rt->CreateCompatibleRenderTarget(bitmapRt.put()));
+
+ winrt::check_hresult(dxgiAPI->writeFactory->CreateTextFormat(L"Segoe UI Variable Text",
+ nullptr,
+ DWRITE_FONT_WEIGHT_NORMAL,
+ DWRITE_FONT_STYLE_NORMAL,
+ DWRITE_FONT_STRETCH_NORMAL,
+ consts::FONT_SIZE * dpiScale,
+ L"en-US",
+ textFormat.put()));
winrt::check_hresult(textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER));
winrt::check_hresult(textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER));
winrt::check_hresult(textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP));
@@ -63,22 +49,22 @@ D2DState::D2DState(const HWND overlayWindow, std::vector solidBrus
solidBrushes.resize(solidBrushesColors.size());
for (size_t i = 0; i < solidBrushes.size(); ++i)
{
- winrt::check_hresult(rt->CreateSolidColorBrush(solidBrushesColors[i], &solidBrushes[i]));
+ winrt::check_hresult(dxgiWindowState.rt->CreateSolidColorBrush(solidBrushesColors[i], solidBrushes[i].put()));
}
- const auto deviceContext = rt.query();
- winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D1Shadow, &shadowEffect));
+ const auto deviceContext = dxgiWindowState.rt.as();
+ winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D1Shadow, shadowEffect.put()));
winrt::check_hresult(shadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, consts::SHADOW_RADIUS));
winrt::check_hresult(shadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, D2D1::ColorF(0.f, 0.f, 0.f, consts::SHADOW_OPACITY)));
- winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D12DAffineTransform, &affineTransformEffect));
+ winrt::check_hresult(deviceContext->CreateEffect(CLSID_D2D12DAffineTransform, affineTransformEffect.put()));
affineTransformEffect->SetInputEffect(0, shadowEffect.get());
- textRenderer = winrt::make_self(d2dFactory, rt, solidBrushes[Brush::foreground]);
+ textRenderer = winrt::make_self(dxgi->d2dFactory2, dxgiWindowState.rt, solidBrushes[Brush::foreground]);
}
void D2DState::DrawTextBox(const wchar_t* text,
- const uint32_t textLen,
+ const size_t textLen,
const std::optional halfOpaqueSymbolPos,
const float centerX,
const float centerY,
@@ -86,16 +72,19 @@ void D2DState::DrawTextBox(const wchar_t* text,
const HWND window) const
{
wil::com_ptr textLayout;
- winrt::check_hresult(writeFactory->CreateTextLayout(text,
- textLen,
- textFormat.get(),
- std::numeric_limits::max(),
- std::numeric_limits::max(),
- &textLayout));
+ winrt::check_hresult(
+ dxgiAPI->writeFactory->CreateTextLayout(text,
+ static_cast(textLen),
+ textFormat.get(),
+ std::numeric_limits::max(),
+ std::numeric_limits::max(),
+ &textLayout));
DWRITE_TEXT_METRICS textMetrics = {};
winrt::check_hresult(textLayout->GetMetrics(&textMetrics));
- textMetrics.width *= consts::TEXT_BOX_MARGIN_COEFF;
- textMetrics.height *= consts::TEXT_BOX_MARGIN_COEFF;
+ // Assumes text doesn't contain new lines
+ const float lineHeight = textMetrics.height;
+ textMetrics.width += lineHeight;
+ textMetrics.height += lineHeight * .5f;
winrt::check_hresult(textLayout->SetMaxWidth(textMetrics.width));
winrt::check_hresult(textLayout->SetMaxHeight(textMetrics.height));
@@ -103,6 +92,8 @@ void D2DState::DrawTextBox(const wchar_t* text,
.top = centerY - textMetrics.height / 2.f,
.right = centerX + textMetrics.width / 2.f,
.bottom = centerY + textMetrics.height / 2.f };
+
+ const float SHADOW_OFFSET = consts::SHADOW_OFFSET * dpiScale;
if (screenQuadrantAware)
{
bool cursorInLeftScreenHalf = false;
@@ -112,8 +103,8 @@ void D2DState::DrawTextBox(const wchar_t* text,
static_cast(centerY),
cursorInLeftScreenHalf,
cursorInTopScreenHalf);
- float textQuadrantOffsetX = textMetrics.width * dpiScale;
- float textQuadrantOffsetY = textMetrics.height * dpiScale;
+ float textQuadrantOffsetX = textMetrics.width / 2.f + SHADOW_OFFSET;
+ float textQuadrantOffsetY = textMetrics.height / 2.f + SHADOW_OFFSET;
if (!cursorInLeftScreenHalf)
textQuadrantOffsetX *= -1.f;
if (!cursorInTopScreenHalf)
@@ -140,15 +131,14 @@ void D2DState::DrawTextBox(const wchar_t* text,
bitmapRt->GetBitmap(&rtBitmap);
shadowEffect->SetInput(0, rtBitmap.get());
- const auto shadowMatrix = D2D1::Matrix3x2F::Translation(consts::SHADOW_OFFSET * dpiScale,
- consts::SHADOW_OFFSET * dpiScale);
+ const auto shadowMatrix = D2D1::Matrix3x2F::Translation(SHADOW_OFFSET, SHADOW_OFFSET);
winrt::check_hresult(affineTransformEffect->SetValue(D2D1_2DAFFINETRANSFORM_PROP_TRANSFORM_MATRIX,
shadowMatrix));
- auto deviceContext = rt.query();
+ auto deviceContext = dxgiWindowState.rt.as();
deviceContext->DrawImage(affineTransformEffect.get(), D2D1_INTERPOLATION_MODE_LINEAR);
// Draw text box border rectangle
- rt->DrawRoundedRectangle(textBoxRect, solidBrushes[Brush::border].get());
+ dxgiWindowState.rt->DrawRoundedRectangle(textBoxRect, solidBrushes[Brush::border].get());
const float TEXT_BOX_PADDING = 1.f * dpiScale;
textBoxRect.rect.bottom -= TEXT_BOX_PADDING;
textBoxRect.rect.top += TEXT_BOX_PADDING;
@@ -156,7 +146,7 @@ void D2DState::DrawTextBox(const wchar_t* text,
textBoxRect.rect.right -= TEXT_BOX_PADDING;
// Draw text & its box
- rt->FillRoundedRectangle(textBoxRect, solidBrushes[Brush::background].get());
+ dxgiWindowState.rt->FillRoundedRectangle(textBoxRect, solidBrushes[Brush::background].get());
if (halfOpaqueSymbolPos.has_value())
{
@@ -167,3 +157,19 @@ void D2DState::DrawTextBox(const wchar_t* text,
}
winrt::check_hresult(textLayout->Draw(nullptr, textRenderer.get(), textRect.left, textRect.top));
}
+
+void D2DState::ToggleAliasedLinesMode(const bool enabled) const
+{
+ if (enabled)
+ {
+ // Draw lines in the middle of a pixel to avoid bleeding, since [0,0] pixel is
+ // a rectangle filled from (0,0) to (1,1) and the lines use thickness = 1.
+ dxgiWindowState.rt->SetTransform(D2D1::Matrix3x2F::Translation(.5f, .5f));
+ dxgiWindowState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
+ }
+ else
+ {
+ dxgiWindowState.rt->SetTransform(D2D1::Matrix3x2F::Identity());
+ dxgiWindowState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
+ }
+}
diff --git a/src/modules/MeasureTool/MeasureToolCore/D2DState.h b/src/modules/MeasureTool/MeasureToolCore/D2DState.h
index 385eb0957be..18bad4536ae 100644
--- a/src/modules/MeasureTool/MeasureToolCore/D2DState.h
+++ b/src/modules/MeasureTool/MeasureToolCore/D2DState.h
@@ -3,10 +3,9 @@
#include
#include
-#include
-#include
#include
+#include "DxgiAPI.h"
#include "PerGlyphOpacityTextRender.h"
enum Brush : size_t
@@ -19,24 +18,27 @@ enum Brush : size_t
struct D2DState
{
- wil::com_ptr d2dFactory;
- wil::com_ptr writeFactory;
- wil::com_ptr rt;
- wil::com_ptr bitmapRt;
- wil::com_ptr textFormat;
- winrt::com_ptr textRenderer;
- std::vector> solidBrushes;
- wil::com_ptr shadowEffect;
- wil::com_ptr affineTransformEffect;
+ const DxgiAPI* dxgiAPI = nullptr;
+
+ DxgiWindowState dxgiWindowState;
+ winrt::com_ptr bitmapRt;
+ winrt::com_ptr textFormat;
+ winrt::com_ptr textRenderer;
+ std::vector> solidBrushes;
+ winrt::com_ptr shadowEffect;
+ winrt::com_ptr affineTransformEffect;
float dpiScale = 1.f;
- D2DState(const HWND window, std::vector solidBrushesColors);
+ D2DState(const DxgiAPI*,
+ HWND window,
+ std::vector solidBrushesColors);
void DrawTextBox(const wchar_t* text,
- const uint32_t textLen,
+ const size_t textLen,
const std::optional halfOpaqueSymbolPos,
const float centerX,
const float centerY,
const bool screenQuadrantAware,
const HWND window) const;
+ void ToggleAliasedLinesMode(const bool enabled) const;
};
diff --git a/src/modules/MeasureTool/MeasureToolCore/DxgiAPI.cpp b/src/modules/MeasureTool/MeasureToolCore/DxgiAPI.cpp
new file mode 100644
index 00000000000..f7e724abb6e
--- /dev/null
+++ b/src/modules/MeasureTool/MeasureToolCore/DxgiAPI.cpp
@@ -0,0 +1,145 @@
+#include "pch.h"
+
+#include "DxgiAPI.h"
+
+#include
+
+//#define DEBUG_DEVICES
+#define SEPARATE_D3D_FOR_CAPTURE
+
+namespace
+{
+ DxgiAPI::D3D CreateD3D()
+ {
+ DxgiAPI::D3D d3d;
+ UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
+#if defined(DEBUG_DEVICES)
+ flags |= D3D11_CREATE_DEVICE_DEBUG;
+#endif
+ HRESULT hr =
+ D3D11CreateDevice(nullptr,
+ D3D_DRIVER_TYPE_HARDWARE,
+ nullptr,
+ flags,
+ nullptr,
+ 0,
+ D3D11_SDK_VERSION,
+ d3d.d3dDevice.put(),
+ nullptr,
+ nullptr);
+ if (hr == DXGI_ERROR_UNSUPPORTED)
+ {
+ hr = D3D11CreateDevice(nullptr,
+ D3D_DRIVER_TYPE_WARP,
+ nullptr,
+ flags,
+ nullptr,
+ 0,
+ D3D11_SDK_VERSION,
+ d3d.d3dDevice.put(),
+ nullptr,
+ nullptr);
+ }
+ winrt::check_hresult(hr);
+
+ d3d.dxgiDevice = d3d.d3dDevice.as();
+ winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(d3d.dxgiDevice.get(), d3d.d3dDeviceInspectable.put()));
+
+ winrt::com_ptr adapter;
+ winrt::check_hresult(d3d.dxgiDevice->GetParent(winrt::guid_of(), adapter.put_void()));
+ winrt::check_hresult(adapter->GetParent(winrt::guid_of(), d3d.dxgiFactory2.put_void()));
+
+ d3d.d3dDevice->GetImmediateContext(d3d.d3dContext.put());
+ winrt::check_bool(d3d.d3dContext);
+ auto contextMultithread = d3d.d3dContext.as();
+ contextMultithread->SetMultithreadProtected(true);
+
+ return d3d;
+ }
+}
+
+DxgiAPI::DxgiAPI()
+{
+ const D2D1_FACTORY_OPTIONS d2dFactoryOptions = {
+#if defined(DEBUG_DEVICES)
+ D2D1_DEBUG_LEVEL_INFORMATION
+#else
+ D2D1_DEBUG_LEVEL_NONE
+#endif
+ };
+
+ winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, d2dFactoryOptions, d2dFactory2.put()));
+
+ winrt::check_hresult(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED,
+ winrt::guid_of(),
+ reinterpret_cast(writeFactory.put())));
+
+ auto d3d = CreateD3D();
+ d3dDevice = d3d.d3dDevice;
+ dxgiDevice = d3d.dxgiDevice;
+ d3dDeviceInspectable = d3d.d3dDeviceInspectable;
+ dxgiFactory2 = d3d.dxgiFactory2;
+ d3dContext = d3d.d3dContext;
+#if defined(SEPARATE_D3D_FOR_CAPTURE)
+ auto d3dFC = CreateD3D();
+ d3dForCapture = d3dFC;
+#else
+ d3dForCapture = d3d;
+#endif
+ winrt::check_hresult(d2dFactory2->CreateDevice(dxgiDevice.get(), d2dDevice1.put()));
+ winrt::check_hresult(DCompositionCreateDevice(
+ dxgiDevice.get(),
+ winrt::guid_of(),
+ compositionDevice.put_void()));
+}
+
+DxgiWindowState DxgiAPI::CreateD2D1RenderTarget(HWND window) const
+{
+ RECT rect = {};
+ winrt::check_bool(GetClientRect(window, &rect));
+
+ const DXGI_SWAP_CHAIN_DESC1 desc = {
+ .Width = static_cast(rect.right - rect.left),
+ .Height = static_cast(rect.bottom - rect.top),
+ .Format = static_cast(winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized),
+ .SampleDesc = { .Count = 1, .Quality = 0 },
+ .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
+ .BufferCount = 2,
+ .Scaling = DXGI_SCALING_STRETCH,
+ .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
+ .AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED,
+ };
+
+ DxgiWindowState state;
+ winrt::com_ptr rt;
+ d2dDevice1->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, rt.put());
+ state.rt = rt;
+
+ winrt::check_hresult(dxgiFactory2->CreateSwapChainForComposition(d3dDevice.get(),
+ &desc,
+ nullptr,
+ state.swapChain.put()));
+ winrt::com_ptr surface;
+ winrt::check_hresult(state.swapChain->GetBuffer(0, winrt::guid_of(), surface.put_void()));
+
+ const D2D1_BITMAP_PROPERTIES1 properties = {
+ .pixelFormat = { .format = DXGI_FORMAT_B8G8R8A8_UNORM, .alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED },
+ .bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW
+ };
+ winrt::com_ptr bitmap;
+ winrt::check_hresult(rt->CreateBitmapFromDxgiSurface(surface.get(),
+ properties,
+ bitmap.put()));
+ rt->SetTarget(bitmap.get());
+ winrt::check_hresult(compositionDevice->CreateTargetForHwnd(window,
+ true,
+ state.compositionTarget.put()));
+
+ winrt::com_ptr visual;
+ winrt::check_hresult(compositionDevice->CreateVisual(visual.put()));
+ winrt::check_hresult(visual->SetContent(state.swapChain.get()));
+ winrt::check_hresult(state.compositionTarget->SetRoot(visual.get()));
+ winrt::check_hresult(compositionDevice->Commit());
+
+ return state;
+}
diff --git a/src/modules/MeasureTool/MeasureToolCore/DxgiAPI.h b/src/modules/MeasureTool/MeasureToolCore/DxgiAPI.h
new file mode 100644
index 00000000000..c18d3b0239d
--- /dev/null
+++ b/src/modules/MeasureTool/MeasureToolCore/DxgiAPI.h
@@ -0,0 +1,49 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+
+struct DxgiWindowState
+{
+ winrt::com_ptr rt;
+ winrt::com_ptr swapChain;
+ winrt::com_ptr compositionTarget;
+};
+
+struct DxgiAPI final
+{
+ struct D3D
+ {
+ winrt::com_ptr d3dDevice;
+ winrt::com_ptr dxgiDevice;
+ winrt::com_ptr d3dDeviceInspectable;
+ winrt::com_ptr dxgiFactory2;
+ winrt::com_ptr d3dContext;
+ };
+
+ winrt::com_ptr d2dFactory2;
+ winrt::com_ptr writeFactory;
+
+ winrt::com_ptr d3dDevice;
+ winrt::com_ptr dxgiDevice;
+ winrt::com_ptr d3dDeviceInspectable;
+ winrt::com_ptr dxgiFactory2;
+ winrt::com_ptr d3dContext;
+
+ D3D d3dForCapture;
+
+ winrt::com_ptr d2dDevice1;
+ winrt::com_ptr compositionDevice;
+
+ DxgiAPI();
+
+ enum class Uninitialized
+ {
+ };
+ explicit inline DxgiAPI(Uninitialized) {}
+
+ DxgiWindowState CreateD2D1RenderTarget(HWND window) const;
+};
\ No newline at end of file
diff --git a/src/modules/MeasureTool/MeasureToolCore/EdgeDetection.cpp b/src/modules/MeasureTool/MeasureToolCore/EdgeDetection.cpp
index 538698a018d..4445c6d1151 100644
--- a/src/modules/MeasureTool/MeasureToolCore/EdgeDetection.cpp
+++ b/src/modules/MeasureTool/MeasureToolCore/EdgeDetection.cpp
@@ -2,8 +2,8 @@
#include "constants.h"
#include "EdgeDetection.h"
+
template
inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, const uint8_t tolerance)
@@ -57,25 +57,21 @@ inline long FindEdge(const BGRATextureView& texture, const POINT centerPoint, co
return Increment ? static_cast(IsX ? texture.width : texture.height) - 1 : 0;
}
-template
+template
inline RECT DetectEdgesInternal(const BGRATextureView& texture,
const POINT centerPoint,
const uint8_t tolerance)
{
return RECT{ .left = FindEdge(texture, centerPoint, tolerance),
.top = FindEdge(texture, centerPoint, tolerance),
.right = FindEdge(texture, centerPoint, tolerance),
.bottom = FindEdge(texture, centerPoint, tolerance) };
}
@@ -83,12 +79,9 @@ inline RECT DetectEdgesInternal(const BGRATextureView& texture,
RECT DetectEdges(const BGRATextureView& texture,
const POINT centerPoint,
const bool perChannel,
- const uint8_t tolerance,
- const bool continuousCapture)
+ const uint8_t tolerance)
{
- auto function = perChannel ? &DetectEdgesInternal : DetectEdgesInternal;
- if (continuousCapture)
- function = perChannel ? &DetectEdgesInternal : &DetectEdgesInternal;
+ auto function = perChannel ? &DetectEdgesInternal : DetectEdgesInternal;
return function(texture, centerPoint, tolerance);
}
diff --git a/src/modules/MeasureTool/MeasureToolCore/EdgeDetection.h b/src/modules/MeasureTool/MeasureToolCore/EdgeDetection.h
index 175c8205404..8942bf24429 100644
--- a/src/modules/MeasureTool/MeasureToolCore/EdgeDetection.h
+++ b/src/modules/MeasureTool/MeasureToolCore/EdgeDetection.h
@@ -5,5 +5,4 @@
RECT DetectEdges(const BGRATextureView& texture,
const POINT centerPoint,
const bool perChannel,
- const uint8_t tolerance,
- const bool continuousCapture);
\ No newline at end of file
+ const uint8_t tolerance);
\ No newline at end of file
diff --git a/src/modules/MeasureTool/MeasureToolCore/MeasureToolOverlayUI.cpp b/src/modules/MeasureTool/MeasureToolCore/MeasureToolOverlayUI.cpp
index 6090a84caee..6c276a0ada3 100644
--- a/src/modules/MeasureTool/MeasureToolCore/MeasureToolOverlayUI.cpp
+++ b/src/modules/MeasureTool/MeasureToolCore/MeasureToolOverlayUI.cpp
@@ -16,30 +16,22 @@ namespace
// Computing in this way to achieve pixel-perfect axial symmetry of aliased D2D lines
if (horizontal)
{
- start.x -= consts::FEET_HALF_LENGTH + 1.f;
- end.x += consts::FEET_HALF_LENGTH;
-
- start.y += 1.f;
- end.y += 1.f;
+ start.x -= consts::FEET_HALF_LENGTH;
+ end.x += consts::FEET_HALF_LENGTH + 1.f;
}
else
{
- start.y -= consts::FEET_HALF_LENGTH + 1.f;
- end.y += consts::FEET_HALF_LENGTH;
-
- start.x += 1.f;
- end.x += 1.f;
+ start.y -= consts::FEET_HALF_LENGTH;
+ end.y += consts::FEET_HALF_LENGTH + 1.f;
}
return { start, end };
}
}
-winrt::com_ptr ConvertID3D11Texture2DToD2D1Bitmap(wil::com_ptr rt,
+winrt::com_ptr ConvertID3D11Texture2DToD2D1Bitmap(winrt::com_ptr rt,
const MappedTextureView* capturedScreenTexture)
{
- std::lock_guard guard{ gpuAccessLock };
-
capturedScreenTexture->view.pixels;
D2D1_BITMAP_PROPERTIES props = { .pixelFormat = rt->GetPixelFormat() };
@@ -62,6 +54,7 @@ LRESULT CALLBACK MeasureToolWndProc(HWND window, UINT message, WPARAM wparam, LP
{
switch (message)
{
+ case WM_MOUSELEAVE:
case WM_CURSOR_LEFT_MONITOR:
{
if (auto state = GetWindowParam*>(window))
@@ -130,20 +123,27 @@ void DrawMeasureToolTick(const CommonState& commonState,
bool drawFeetOnCross = {};
bool drawHorizontalCrossLine = true;
bool drawVerticalCrossLine = true;
- RECT measuredEdges{};
+
+ Measurement measuredEdges{};
MeasureToolState::Mode mode = {};
winrt::com_ptr backgroundBitmap;
const MappedTextureView* backgroundTextureToConvert = nullptr;
+ bool gotMeasurement = false;
toolState.Read([&](const MeasureToolState& state) {
continuousCapture = state.global.continuousCapture;
drawFeetOnCross = state.global.drawFeetOnCross;
mode = state.global.mode;
-
if (auto it = state.perScreen.find(window); it != end(state.perScreen))
{
const auto& perScreen = it->second;
- measuredEdges = perScreen.measuredEdges;
+ if (!perScreen.measuredEdges)
+ {
+ return;
+ }
+
+ gotMeasurement = true;
+ measuredEdges = *perScreen.measuredEdges;
if (continuousCapture)
return;
@@ -158,6 +158,10 @@ void DrawMeasureToolTick(const CommonState& commonState,
}
}
});
+
+ if (!gotMeasurement)
+ return;
+
switch (mode)
{
case MeasureToolState::Mode::Cross:
@@ -176,7 +180,7 @@ void DrawMeasureToolTick(const CommonState& commonState,
if (!continuousCapture && !backgroundBitmap && backgroundTextureToConvert)
{
- backgroundBitmap = ConvertID3D11Texture2DToD2D1Bitmap(d2dState.rt, backgroundTextureToConvert);
+ backgroundBitmap = ConvertID3D11Texture2DToD2D1Bitmap(d2dState.dxgiWindowState.rt, backgroundTextureToConvert);
if (backgroundBitmap)
{
toolState.Access([&](MeasureToolState& state) {
@@ -187,28 +191,24 @@ void DrawMeasureToolTick(const CommonState& commonState,
}
if (continuousCapture || !backgroundBitmap)
- d2dState.rt->Clear();
+ d2dState.dxgiWindowState.rt->Clear();
- // Add 1px to each dim, since the range we obtain from measuredEdges is inclusive.
- const float hMeasure = static_cast(measuredEdges.right - measuredEdges.left + 1);
- const float vMeasure = static_cast(measuredEdges.bottom - measuredEdges.top + 1);
+ const float hMeasure = measuredEdges.Width(Measurement::Unit::Pixel);
+ const float vMeasure = measuredEdges.Height(Measurement::Unit::Pixel);
if (!continuousCapture && backgroundBitmap)
{
- d2dState.rt->DrawBitmap(backgroundBitmap.get());
+ d2dState.dxgiWindowState.rt->DrawBitmap(backgroundBitmap.get());
}
- const auto previousAliasingMode = d2dState.rt->GetAntialiasMode();
- // Anti-aliasing is creating artifacts. Aliasing is for drawing straight lines.
- d2dState.rt->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
-
- const auto cursorPos = convert::FromSystemToRelativeForDirect2D(window, commonState.cursorPosSystemSpace);
+ const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
+ d2dState.ToggleAliasedLinesMode(true);
if (drawHorizontalCrossLine)
{
- const D2D_POINT_2F hLineStart{ .x = static_cast(measuredEdges.left), .y = static_cast(cursorPos.y) };
+ const D2D_POINT_2F hLineStart{ .x = measuredEdges.rect.left, .y = static_cast(cursorPos.y) };
D2D_POINT_2F hLineEnd{ .x = hLineStart.x + hMeasure, .y = hLineStart.y };
- d2dState.rt->DrawLine(hLineStart, hLineEnd, d2dState.solidBrushes[Brush::line].get());
+ d2dState.dxgiWindowState.rt->DrawLine(hLineStart, hLineEnd, d2dState.solidBrushes[Brush::line].get());
if (drawFeetOnCross)
{
@@ -218,57 +218,37 @@ void DrawMeasureToolTick(const CommonState& commonState,
hLineEnd.x -= 1.f;
auto [left_start, left_end] = ComputeCrossFeetLine(hLineStart, false);
auto [right_start, right_end] = ComputeCrossFeetLine(hLineEnd, false);
- d2dState.rt->DrawLine(left_start, left_end, d2dState.solidBrushes[Brush::line].get());
- d2dState.rt->DrawLine(right_start, right_end, d2dState.solidBrushes[Brush::line].get());
+ d2dState.dxgiWindowState.rt->DrawLine(left_start, left_end, d2dState.solidBrushes[Brush::line].get());
+ d2dState.dxgiWindowState.rt->DrawLine(right_start, right_end, d2dState.solidBrushes[Brush::line].get());
}
}
if (drawVerticalCrossLine)
{
- const D2D_POINT_2F vLineStart{ .x = static_cast(cursorPos.x), .y = static_cast(measuredEdges.top) };
+ const D2D_POINT_2F vLineStart{ .x = static_cast(cursorPos.x), .y = measuredEdges.rect.top };
D2D_POINT_2F vLineEnd{ .x = vLineStart.x, .y = vLineStart.y + vMeasure };
- d2dState.rt->DrawLine(vLineStart, vLineEnd, d2dState.solidBrushes[Brush::line].get());
+ d2dState.dxgiWindowState.rt->DrawLine(vLineStart, vLineEnd, d2dState.solidBrushes[Brush::line].get());
if (drawFeetOnCross)
{
vLineEnd.y -= 1.f;
auto [top_start, top_end] = ComputeCrossFeetLine(vLineStart, true);
auto [bottom_start, bottom_end] = ComputeCrossFeetLine(vLineEnd, true);
- d2dState.rt->DrawLine(top_start, top_end, d2dState.solidBrushes[Brush::line].get());
- d2dState.rt->DrawLine(bottom_start, bottom_end, d2dState.solidBrushes[Brush::line].get());
+ d2dState.dxgiWindowState.rt->DrawLine(top_start, top_end, d2dState.solidBrushes[Brush::line].get());
+ d2dState.dxgiWindowState.rt->DrawLine(bottom_start, bottom_end, d2dState.solidBrushes[Brush::line].get());
}
}
- // After drawing the lines, restore anti aliasing to draw the measurement tooltip.
- d2dState.rt->SetAntialiasMode(previousAliasingMode);
-
- uint32_t measureStringBufLen = 0;
+ d2dState.ToggleAliasedLinesMode(false);
OverlayBoxText text;
- std::optional crossSymbolPos;
- switch (mode)
- {
- case MeasureToolState::Mode::Cross:
- measureStringBufLen = swprintf_s(text.buffer.data(),
- text.buffer.size(),
- L"%.0f × %.0f",
- hMeasure,
- vMeasure);
- crossSymbolPos = wcschr(text.buffer.data(), L' ') - text.buffer.data() + 1;
- break;
- case MeasureToolState::Mode::Vertical:
- measureStringBufLen = swprintf_s(text.buffer.data(),
- text.buffer.size(),
- L"%.0f",
- vMeasure);
- break;
- case MeasureToolState::Mode::Horizontal:
- measureStringBufLen = swprintf_s(text.buffer.data(),
- text.buffer.size(),
- L"%.0f",
- hMeasure);
- break;
- }
+
+ const auto [crossSymbolPos, measureStringBufLen] =
+ measuredEdges.Print(text.buffer.data(),
+ text.buffer.size(),
+ drawHorizontalCrossLine,
+ drawVerticalCrossLine,
+ commonState.units);
commonState.overlayBoxText.Access([&](OverlayBoxText& v) {
v = text;
diff --git a/src/modules/MeasureTool/MeasureToolCore/Measurement.cpp b/src/modules/MeasureTool/MeasureToolCore/Measurement.cpp
new file mode 100644
index 00000000000..614d9dd1c1f
--- /dev/null
+++ b/src/modules/MeasureTool/MeasureToolCore/Measurement.cpp
@@ -0,0 +1,91 @@
+#include "pch.h"
+
+#include "Measurement.h"
+
+Measurement::Measurement(RECT winRect)
+{
+ rect.left = static_cast(winRect.left);
+ rect.right = static_cast(winRect.right);
+ rect.top = static_cast(winRect.top);
+ rect.bottom = static_cast(winRect.bottom);
+}
+
+Measurement::Measurement(D2D1_RECT_F d2dRect) :
+ rect{ d2dRect }
+{
+}
+
+namespace
+{
+ inline float Convert(const float pixels, const Measurement::Unit units)
+ {
+ switch (units)
+ {
+ case Measurement::Unit::Pixel:
+ return pixels;
+ case Measurement::Unit::Inch:
+ return pixels / 96.f;
+ case Measurement::Unit::Centimetre:
+ return pixels / 96.f * 2.54f;
+ default:
+ return pixels;
+ }
+ }
+}
+
+inline float Measurement::Width(const Unit units) const
+{
+ return Convert(rect.right - rect.left + 1.f, units);
+}
+
+inline float Measurement::Height(const Unit units) const
+{
+ return Convert(rect.bottom - rect.top + 1.f, units);
+}
+
+Measurement::PrintResult Measurement::Print(wchar_t* buf,
+ const size_t bufSize,
+ const bool printWidth,
+ const bool printHeight,
+ const Unit units) const
+{
+ PrintResult result;
+ if (printWidth)
+ {
+ result.strLen += swprintf_s(buf,
+ bufSize,
+ L"%g",
+ Width(units));
+ if (printHeight)
+ {
+ result.crossSymbolPos = result.strLen + 1;
+ result.strLen += swprintf_s(buf + result.strLen,
+ bufSize - result.strLen,
+ L" \x00D7 ");
+ }
+ }
+
+ if (printHeight)
+ {
+ result.strLen += swprintf_s(buf + result.strLen,
+ bufSize - result.strLen,
+ L"%g",
+ Height(units));
+ }
+
+ switch (units)
+ {
+ case Measurement::Unit::Inch:
+ result.strLen += swprintf_s(buf + result.strLen,
+ bufSize - result.strLen,
+ L" in");
+ break;
+ case Measurement::Unit::Centimetre:
+ result.strLen += swprintf_s(buf + result.strLen,
+ bufSize - result.strLen,
+ L" cm");
+ break;
+ }
+
+ return result;
+}
diff --git a/src/modules/MeasureTool/MeasureToolCore/Measurement.h b/src/modules/MeasureTool/MeasureToolCore/Measurement.h
new file mode 100644
index 00000000000..fb8263566e3
--- /dev/null
+++ b/src/modules/MeasureTool/MeasureToolCore/Measurement.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include
+#include
+
+struct Measurement
+{
+ enum Unit
+ {
+ Pixel,
+ Inch,
+ Centimetre
+ };
+
+ D2D1_RECT_F rect = {}; // corners are inclusive
+
+ Measurement() = default;
+ Measurement(const Measurement&) = default;
+ Measurement& operator=(const Measurement&) = default;
+
+ explicit Measurement(D2D1_RECT_F d2dRect);
+ explicit Measurement(RECT winRect);
+
+ float Width(const Unit units) const;
+ float Height(const Unit units) const;
+
+ struct PrintResult
+ {
+ std::optional crossSymbolPos;
+ size_t strLen = {};
+ };
+
+ PrintResult Print(wchar_t* buf,
+ const size_t bufSize,
+ const bool printWidth,
+ const bool printHeight,
+ const Unit units) const;
+};
diff --git a/src/modules/MeasureTool/MeasureToolCore/OverlayUI.cpp b/src/modules/MeasureTool/MeasureToolCore/OverlayUI.cpp
index 8ede412f24d..ac307ed836b 100644
--- a/src/modules/MeasureTool/MeasureToolCore/OverlayUI.cpp
+++ b/src/modules/MeasureTool/MeasureToolCore/OverlayUI.cpp
@@ -1,6 +1,7 @@
#include "pch.h"
#include "BoundsToolOverlayUI.h"
+#include "constants.h"
#include "MeasureToolOverlayUI.h"
#include "OverlayUI.h"
@@ -22,16 +23,17 @@ void CreateOverlayWindowClasses()
wcex.lpfnWndProc = MeasureToolWndProc;
wcex.lpszClassName = NonLocalizable::MeasureToolOverlayWindowName;
+ wcex.hCursor = LoadCursorW(nullptr, IDC_CROSS);
RegisterClassExW(&wcex);
wcex.lpfnWndProc = BoundsToolWndProc;
wcex.lpszClassName = NonLocalizable::BoundsToolOverlayWindowName;
- wcex.hCursor = LoadCursorW(nullptr, IDC_CROSS);
RegisterClassExW(&wcex);
}
HWND CreateOverlayUIWindow(const CommonState& commonState,
const MonitorInfo& monitor,
+ const bool excludeFromCapture,
const wchar_t* windowClass,
void* extraParam)
{
@@ -39,7 +41,7 @@ HWND CreateOverlayUIWindow(const CommonState& commonState,
std::call_once(windowClassesCreatedFlag, CreateOverlayWindowClasses);
const auto screenArea = monitor.GetScreenSize(true);
- DWORD windowStyle = WS_EX_TOOLWINDOW;
+ DWORD windowStyle = WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW;
#if !defined(DEBUG_OVERLAY)
windowStyle |= WS_EX_TOPMOST;
#endif
@@ -47,7 +49,7 @@ HWND CreateOverlayUIWindow(const CommonState& commonState,
CreateWindowExW(windowStyle,
windowClass,
L"PowerToys.MeasureToolOverlay",
- WS_POPUP,
+ WS_POPUP | CS_HREDRAW | CS_VREDRAW,
screenArea.left(),
screenArea.top(),
screenArea.width(),
@@ -59,7 +61,11 @@ HWND CreateOverlayUIWindow(const CommonState& commonState,
};
winrt::check_bool(window);
ShowWindow(window, SW_SHOWNORMAL);
- SetWindowDisplayAffinity(window, WDA_EXCLUDEFROMCAPTURE);
+ UpdateWindow(window);
+ if (excludeFromCapture)
+ {
+ SetWindowDisplayAffinity(window, WDA_EXCLUDEFROMCAPTURE);
+ }
#if !defined(DEBUG_OVERLAY)
SetWindowPos(window, HWND_TOPMOST, {}, {}, {}, {}, SWP_NOMOVE | SWP_NOSIZE);
#else
@@ -109,51 +115,60 @@ std::vector AppendCommonOverlayUIColors(const D2D1::ColorF& lineCo
void OverlayUIState::RunUILoop()
{
+ bool cursorOnScreen = true;
+
while (IsWindow(_window) && !_commonState.closeOnOtherMonitors)
{
+ const auto now = std::chrono::high_resolution_clock::now();
const auto cursor = _commonState.cursorPosSystemSpace;
- const bool cursorOnScreen = _monitorArea.inside(cursor);
const bool cursorOverToolbar = _commonState.toolbarBoundingBox.inside(cursor);
-
- if (cursorOnScreen != _cursorOnScreen)
+ auto& dxgi = _d2dState.dxgiWindowState;
+ if (_monitorArea.inside(cursor) != cursorOnScreen)
{
- _cursorOnScreen = cursorOnScreen;
+ cursorOnScreen = !cursorOnScreen;
if (!cursorOnScreen)
{
- if (_clearOnCursorLeavingScreen)
- {
- _d2dState.rt->BeginDraw();
- _d2dState.rt->Clear();
- _d2dState.rt->EndDraw();
- }
PostMessageW(_window, WM_CURSOR_LEFT_MONITOR, {}, {});
}
}
+ run_message_loop(true, 1);
+
+ dxgi.rt->BeginDraw();
+ dxgi.rt->Clear();
+
+ if (!cursorOverToolbar)
+ _tickFunc();
+
+ dxgi.rt->EndDraw();
+ dxgi.swapChain->Present(0, 0);
+
if (cursorOnScreen)
{
- _d2dState.rt->BeginDraw();
- if (!cursorOverToolbar)
- _tickFunc();
- else
- _d2dState.rt->Clear();
-
- _d2dState.rt->EndDraw();
+ const auto frameTime = std::chrono::high_resolution_clock::now() - now;
+ if (frameTime < consts::TARGET_FRAME_DURATION)
+ {
+ std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION - frameTime);
+ }
+ }
+ else
+ {
+ // Don't consume resources while nothing could be updated
+ std::this_thread::sleep_for(std::chrono::milliseconds{ 200 });
}
-
- run_message_loop(true, 1);
}
DestroyWindow(_window);
}
template
-OverlayUIState::OverlayUIState(StateT& toolState,
+OverlayUIState::OverlayUIState(const DxgiAPI* dxgiAPI,
+ StateT& toolState,
TickFuncT tickFunc,
const CommonState& commonState,
HWND window) :
_window{ window },
_commonState{ commonState },
- _d2dState{ window, AppendCommonOverlayUIColors(commonState.lineColor) },
+ _d2dState{ dxgiAPI, window, AppendCommonOverlayUIColors(commonState.lineColor) },
_tickFunc{ [this, tickFunc, &toolState] {
tickFunc(_commonState, toolState, _window, _d2dState);
} }
@@ -175,25 +190,30 @@ OverlayUIState::~OverlayUIState()
// Returning unique_ptr, since we need to pin ui state in memory
template
-inline std::unique_ptr OverlayUIState::CreateInternal(ToolT& toolState,
+inline std::unique_ptr OverlayUIState::CreateInternal(const DxgiAPI* dxgi,
+ ToolT& toolState,
TickFuncT tickFunc,
CommonState& commonState,
const wchar_t* toolWindowClassName,
void* windowParam,
const MonitorInfo& monitor,
- const bool clearOnCursorLeavingScreen)
+ const bool excludeFromCapture)
{
wil::shared_event uiCreatedEvent(wil::EventOptions::ManualReset);
std::unique_ptr uiState;
- auto threadHandle = SpawnLoggedThread(L"OverlayUI thread", [&] {
- const HWND window = CreateOverlayUIWindow(commonState, monitor, toolWindowClassName, windowParam);
- uiState = std::unique_ptr{ new OverlayUIState{ toolState, tickFunc, commonState, window } };
- uiState->_monitorArea = monitor.GetScreenSize(true);
- uiState->_clearOnCursorLeavingScreen = clearOnCursorLeavingScreen;
- // we must create window + d2d state in the same thread, then store thread handle in uiState, thus
- // lifetime is ok here, since we join the thread in destructor
- auto* state = uiState.get();
- uiCreatedEvent.SetEvent();
+ std::thread threadHandle = SpawnLoggedThread(L"OverlayUI thread", [&] {
+ OverlayUIState* state = nullptr;
+ {
+ auto sinalUICreatedEvent = wil::scope_exit([&] { uiCreatedEvent.SetEvent(); });
+
+ const HWND window = CreateOverlayUIWindow(commonState, monitor, excludeFromCapture, toolWindowClassName, windowParam);
+
+ uiState = std::unique_ptr{ new OverlayUIState{ dxgi, toolState, tickFunc, commonState, window } };
+ uiState->_monitorArea = monitor.GetScreenSize(true);
+ // we must create window + d2d state in the same thread, then store thread handle in uiState, thus
+ // lifetime is ok here, since we join the thread in destructor
+ state = uiState.get();
+ }
state->RunUILoop();
@@ -202,28 +222,40 @@ inline std::unique_ptr OverlayUIState::CreateInternal(ToolT& too
});
uiCreatedEvent.wait();
- uiState->_uiThread = std::move(threadHandle);
+ if (uiState)
+ uiState->_uiThread = std::move(threadHandle);
+ else if (threadHandle.joinable())
+ threadHandle.join();
+
return uiState;
}
-std::unique_ptr OverlayUIState::Create(Serialized& toolState,
+std::unique_ptr OverlayUIState::Create(const DxgiAPI* dxgi,
+ Serialized& toolState,
CommonState& commonState,
const MonitorInfo& monitor)
{
- return OverlayUIState::CreateInternal(toolState,
+ bool excludeFromCapture = false;
+ toolState.Read([&](const MeasureToolState& s) {
+ excludeFromCapture = s.global.continuousCapture;
+ });
+ return OverlayUIState::CreateInternal(dxgi,
+ toolState,
DrawMeasureToolTick,
commonState,
NonLocalizable::MeasureToolOverlayWindowName,
&toolState,
monitor,
- true);
+ excludeFromCapture);
}
-std::unique_ptr OverlayUIState::Create(BoundsToolState& toolState,
+std::unique_ptr OverlayUIState::Create(const DxgiAPI* dxgi,
+ BoundsToolState& toolState,
CommonState& commonState,
const MonitorInfo& monitor)
{
- return OverlayUIState::CreateInternal(toolState,
+ return OverlayUIState::CreateInternal(dxgi,
+ toolState,
DrawBoundsToolTick,
commonState,
NonLocalizable::BoundsToolOverlayWindowName,
diff --git a/src/modules/MeasureTool/MeasureToolCore/OverlayUI.h b/src/modules/MeasureTool/MeasureToolCore/OverlayUI.h
index e4d2b1ddfeb..5f1b7be39d3 100644
--- a/src/modules/MeasureTool/MeasureToolCore/OverlayUI.h
+++ b/src/modules/MeasureTool/MeasureToolCore/OverlayUI.h
@@ -1,6 +1,8 @@
#pragma once
+#include "DxgiAPI.h"
#include "D2DState.h"
+
#include "ToolState.h"
#include
@@ -9,7 +11,8 @@
class OverlayUIState final
{
template
- OverlayUIState(StateT& toolState,
+ OverlayUIState(const DxgiAPI* dxgiAPI,
+ StateT& toolState,
TickFuncT tickFunc,
const CommonState& commonState,
HWND window);
@@ -20,26 +23,27 @@ class OverlayUIState final
D2DState _d2dState;
std::function _tickFunc;
std::thread _uiThread;
- bool _cursorOnScreen = true;
- bool _clearOnCursorLeavingScreen = false;
template
- static std::unique_ptr CreateInternal(ToolT& toolState,
+ static std::unique_ptr CreateInternal(const DxgiAPI* dxgi,
+ ToolT& toolState,
TickFuncT tickFunc,
CommonState& commonState,
const wchar_t* toolWindowClassName,
void* windowParam,
const MonitorInfo& monitor,
- const bool clearOnCursorLeavingScreen);
+ const bool excludeFromCapture);
public:
OverlayUIState(OverlayUIState&&) noexcept = default;
~OverlayUIState();
- static std::unique_ptr Create(BoundsToolState& toolState,
+ static std::unique_ptr Create(const DxgiAPI* dxgi,
+ BoundsToolState& toolState,
CommonState& commonState,
const MonitorInfo& monitor);
- static std::unique_ptr Create(Serialized& toolState,
+ static std::unique_ptr Create(const DxgiAPI* dxgi,
+ Serialized& toolState,
CommonState& commonState,
const MonitorInfo& monitor);
inline HWND overlayWindowHandle() const
diff --git a/src/modules/MeasureTool/MeasureToolCore/PerGlyphOpacityTextRender.cpp b/src/modules/MeasureTool/MeasureToolCore/PerGlyphOpacityTextRender.cpp
index 4ccd9d9f0e0..9325255d7e8 100644
--- a/src/modules/MeasureTool/MeasureToolCore/PerGlyphOpacityTextRender.cpp
+++ b/src/modules/MeasureTool/MeasureToolCore/PerGlyphOpacityTextRender.cpp
@@ -3,9 +3,9 @@
#include "PerGlyphOpacityTextRender.h"
PerGlyphOpacityTextRender::PerGlyphOpacityTextRender(
- wil::com_ptr pD2DFactory,
- wil::com_ptr rt,
- wil::com_ptr baseBrush) :
+ winrt::com_ptr pD2DFactory,
+ winrt::com_ptr rt,
+ winrt::com_ptr baseBrush) :
_pD2DFactory{ pD2DFactory.get() },
_rt{ rt.get() },
_baseBrush{ baseBrush.get() }
diff --git a/src/modules/MeasureTool/MeasureToolCore/PerGlyphOpacityTextRender.h b/src/modules/MeasureTool/MeasureToolCore/PerGlyphOpacityTextRender.h
index d20b6dfce5c..3ce874f38c2 100644
--- a/src/modules/MeasureTool/MeasureToolCore/PerGlyphOpacityTextRender.h
+++ b/src/modules/MeasureTool/MeasureToolCore/PerGlyphOpacityTextRender.h
@@ -17,13 +17,13 @@ struct OpacityEffect : winrt::implements
struct PerGlyphOpacityTextRender : winrt::implements
{
ID2D1Factory* _pD2DFactory = nullptr;
- ID2D1HwndRenderTarget* _rt = nullptr;
+ ID2D1RenderTarget* _rt = nullptr;
ID2D1SolidColorBrush* _baseBrush = nullptr;
PerGlyphOpacityTextRender(
- wil::com_ptr pD2DFactory,
- wil::com_ptr rt,
- wil::com_ptr baseBrush);
+ winrt::com_ptr pD2DFactory,
+ winrt::com_ptr rt,
+ winrt::com_ptr baseBrush);
HRESULT __stdcall DrawGlyphRun(void* clientDrawingContext,
FLOAT baselineOriginX,
diff --git a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.cpp b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.cpp
index f7e613a2b6e..d4f90ba678b 100644
--- a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.cpp
+++ b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.cpp
@@ -3,6 +3,7 @@
#include
#include
#include
+#include
#include
#include "../MeasureToolModuleInterface/trace.h"
@@ -14,8 +15,6 @@
//#define DEBUG_PRIMARY_MONITOR_ONLY
-std::recursive_mutex gpuAccessLock;
-
namespace winrt::PowerToys::MeasureToolCore::implementation
{
void Core::MouseCaptureThread()
@@ -34,17 +33,33 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
_stopMouseCaptureThreadSignal{ wil::EventOptions::ManualReset },
_mouseCaptureThread{ [this] { MouseCaptureThread(); } }
{
- Trace::RegisterProvider();
- LoggerHelpers::init_logger(L"Measure Tool", L"Core", "Measure Tool");
}
Core::~Core()
{
- _stopMouseCaptureThreadSignal.SetEvent();
- _mouseCaptureThread.join();
+ Close();
+ }
+ void Core::Close()
+ {
ResetState();
- Trace::UnregisterProvider();
+
+ // avoid triggering d2d debug layer leak on shutdown
+ dxgiAPI = DxgiAPI{ DxgiAPI::Uninitialized{} };
+
+#if 0
+ winrt::com_ptr dxgiDebug;
+ winrt::check_hresult(DXGIGetDebugInterface1({},
+ winrt::guid_of(),
+ dxgiDebug.put_void()));
+ dxgiDebug->ReportLiveObjects(DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL);
+#endif
+
+ if (!_stopMouseCaptureThreadSignal.is_signaled())
+ _stopMouseCaptureThreadSignal.SetEvent();
+
+ if (_mouseCaptureThread.joinable())
+ _mouseCaptureThread.join();
}
void Core::ResetState()
@@ -67,6 +82,7 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
_settings = Settings::LoadFromFile();
+ _commonState.units = _settings.units;
_commonState.lineColor.r = _settings.lineColor[0] / 255.f;
_commonState.lineColor.g = _settings.lineColor[1] / 255.f;
_commonState.lineColor.b = _settings.lineColor[2] / 255.f;
@@ -78,13 +94,15 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
ResetState();
#if defined(DEBUG_PRIMARY_MONITOR_ONLY)
- const auto& monitorInfo = MonitorInfo::GetPrimaryMonitor();
+ std::vector monitors = { MonitorInfo::GetPrimaryMonitor() };
+ const auto& monitorInfo = monitors[0];
#else
const auto monitors = MonitorInfo::GetMonitors(true);
for (const auto& monitorInfo : monitors)
#endif
{
- auto overlayUI = OverlayUIState::Create(_boundsToolState,
+ auto overlayUI = OverlayUIState::Create(&dxgiAPI,
+ _boundsToolState,
_commonState,
monitorInfo);
#if !defined(DEBUG_PRIMARY_MONITOR_ONLY)
@@ -120,7 +138,8 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
for (const auto& monitorInfo : monitors)
#endif
{
- auto overlayUI = OverlayUIState::Create(_measureToolState,
+ auto overlayUI = OverlayUIState::Create(&dxgiAPI,
+ _measureToolState,
_commonState,
monitorInfo);
#if !defined(DEBUG_PRIMARY_MONITOR_ONLY)
@@ -133,7 +152,7 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
for (size_t i = 0; i < monitors.size(); ++i)
{
auto thread = StartCapturingThread(
- &_d3dState,
+ &dxgiAPI,
_commonState,
_measureToolState,
_overlayUIStates[i]->overlayWindowHandle(),
diff --git a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.h b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.h
index 4cb91d1069b..79728794a92 100644
--- a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.h
+++ b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.h
@@ -8,12 +8,29 @@
#include
#include "ScreenCapturing.h"
+struct PowerToysMisc
+{
+ PowerToysMisc()
+ {
+ Trace::RegisterProvider();
+ LoggerHelpers::init_logger(L"Measure Tool", L"Core", "Measure Tool");
+ InitUnhandledExceptionHandler();
+ }
+
+ ~PowerToysMisc()
+ {
+ Trace::UnregisterProvider();
+ }
+};
+
namespace winrt::PowerToys::MeasureToolCore::implementation
{
- struct Core : CoreT
+ struct Core : PowerToysMisc, CoreT
{
Core();
~Core();
+ void Close();
+
void StartBoundsTool();
void StartMeasureTool(const bool horizontal, const bool vertical);
void SetToolCompletionEvent(ToolSessionCompleted sessionCompletedTrigger);
@@ -22,7 +39,7 @@ namespace winrt::PowerToys::MeasureToolCore::implementation
float GetDPIScaleForWindow(uint64_t windowHandle);
void MouseCaptureThread();
- D3DState _d3dState;
+ DxgiAPI dxgiAPI;
wil::shared_event _stopMouseCaptureThreadSignal;
std::thread _mouseCaptureThread;
diff --git a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.idl b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.idl
index 95e68910a14..8704c0b6cd7 100644
--- a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.idl
+++ b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.idl
@@ -11,7 +11,7 @@ namespace PowerToys
delegate void ToolSessionCompleted();
[default_interface]
- runtimeclass Core
+ runtimeclass Core : Windows.Foundation.IClosable
{
Core();
void SetToolCompletionEvent(event ToolSessionCompleted completionTrigger);
diff --git a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj
index 9780d0d09aa..fed339372db 100644
--- a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj
+++ b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj
@@ -55,7 +55,7 @@
Windows
false
MeasureTool.def
- Shell32.lib;Shcore.lib;Dwmapi.lib;Gdi32.lib;%(AdditionalDependencies)
+ Dbghelp.lib;Shell32.lib;Shcore.lib;dcomp.lib;DXGI.lib;Dwmapi.lib;Gdi32.lib;%(AdditionalDependencies)
@@ -74,8 +74,11 @@
+
+
+
@@ -96,7 +99,9 @@
+
+
diff --git a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj.filters b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj.filters
index 316a5b7aa68..b2ce8ec14bf 100644
--- a/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj.filters
+++ b/src/modules/MeasureTool/MeasureToolCore/PowerToys.MeasureToolCore.vcxproj.filters
@@ -17,6 +17,8 @@
+
+
@@ -34,6 +36,9 @@
+
+
+
diff --git a/src/modules/MeasureTool/MeasureToolCore/ScreenCapturing.cpp b/src/modules/MeasureTool/MeasureToolCore/ScreenCapturing.cpp
index 2f95d175443..6b62b2db594 100644
--- a/src/modules/MeasureTool/MeasureToolCore/ScreenCapturing.cpp
+++ b/src/modules/MeasureTool/MeasureToolCore/ScreenCapturing.cpp
@@ -10,65 +10,47 @@
//#define DEBUG_EDGES
-D3DState::D3DState()
+namespace
{
- 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)
+ winrt::GraphicsCaptureItem CreateCaptureItemForMonitor(HMONITOR monitor)
{
- hr = D3D11CreateDevice(nullptr,
- D3D_DRIVER_TYPE_WARP,
- nullptr,
- flags,
- nullptr,
- 0,
- D3D11_SDK_VERSION,
- d3dDevice.put(),
- nullptr,
- nullptr);
- }
- winrt::check_hresult(hr);
+ auto captureInterop = winrt::get_activation_factory<
+ winrt::GraphicsCaptureItem,
+ IGraphicsCaptureItemInterop>();
+
+ winrt::GraphicsCaptureItem item = nullptr;
- dxgiDevice = d3dDevice.as();
- winrt::check_hresult(CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), d3dDeviceInspectable.put()));
+ winrt::check_hresult(captureInterop->CreateForMonitor(
+ monitor,
+ winrt::guid_of(),
+ winrt::put_abi(item)));
+
+ return item;
+ }
}
class D3DCaptureState final
{
- D3DState* d3dState = nullptr;
+ DxgiAPI* dxgiAPI = nullptr;
winrt::IDirect3DDevice device;
winrt::com_ptr swapChain;
- winrt::com_ptr context;
- winrt::SizeInt32 frameSize;
+ winrt::SizeInt32 frameSize;
+ HMONITOR monitor = {};
winrt::DirectXPixelFormat pixelFormat;
- winrt::Direct3D11CaptureFramePool framePool;
- winrt::GraphicsCaptureSession session;
+
+ winrt::Direct3D11CaptureFramePool framePool = nullptr;
+ winrt::GraphicsCaptureSession session = nullptr;
std::function frameCallback;
Box monitorArea;
- bool captureOutsideOfMonitor = false;
+ bool continuousCapture = false;
- D3DCaptureState(D3DState* d3dState,
- winrt::com_ptr _swapChain,
- winrt::com_ptr _context,
- const winrt::GraphicsCaptureItem& item,
- winrt::DirectXPixelFormat _pixelFormat,
- Box monitorArea,
- const bool captureOutsideOfMonitor);
+ D3DCaptureState(DxgiAPI* dxgiAPI,
+ winrt::com_ptr swapChain,
+ winrt::DirectXPixelFormat pixelFormat,
+ MonitorInfo monitorInfo,
+ const bool continuousCapture);
winrt::com_ptr CopyFrameToCPU(const winrt::com_ptr& texture);
@@ -76,14 +58,13 @@ class D3DCaptureState final
void StartSessionInPreferredMode();
- std::mutex destructorMutex;
+ std::mutex frameArrivedMutex;
public:
- static std::unique_ptr Create(D3DState* d3dState,
- winrt::GraphicsCaptureItem item,
+ static std::unique_ptr Create(DxgiAPI* dxgiAPI,
+ MonitorInfo monitorInfo,
const winrt::DirectXPixelFormat pixelFormat,
- Box monitorSize,
- const bool captureOutsideOfMonitor);
+ const bool continuousCapture);
~D3DCaptureState();
@@ -93,25 +74,19 @@ class D3DCaptureState final
void StopCapture();
};
-D3DCaptureState::D3DCaptureState(D3DState* _d3dState,
+D3DCaptureState::D3DCaptureState(DxgiAPI* dxgiAPI,
winrt::com_ptr _swapChain,
- winrt::com_ptr _context,
- const winrt::GraphicsCaptureItem& item,
- winrt::DirectXPixelFormat _pixelFormat,
- Box _monitorArea,
- const bool _captureOutsideOfMonitor) :
- d3dState{ _d3dState },
- device{ _d3dState->d3dDeviceInspectable.as() },
+ winrt::DirectXPixelFormat pixelFormat_,
+ MonitorInfo monitorInfo,
+ const bool continuousCapture_) :
+ dxgiAPI{ dxgiAPI },
+ device{ dxgiAPI->d3dForCapture.d3dDeviceInspectable.as() },
swapChain{ std::move(_swapChain) },
- context{ std::move(_context) },
- frameSize{ item.Size() },
- pixelFormat{ std::move(_pixelFormat) },
- framePool{ winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(device, pixelFormat, 1, item.Size()) },
- session{ framePool.CreateCaptureSession(item) },
- monitorArea{ _monitorArea },
- captureOutsideOfMonitor{ _captureOutsideOfMonitor }
+ pixelFormat{ std::move(pixelFormat_) },
+ monitor{ monitorInfo.GetHandle() },
+ monitorArea{ monitorInfo.GetScreenSize(true) },
+ continuousCapture{ continuousCapture_ }
{
- framePool.FrameArrived({ this, &D3DCaptureState::OnFrameArrived });
}
winrt::com_ptr D3DCaptureState::CopyFrameToCPU(const winrt::com_ptr& frameTexture)
@@ -124,8 +99,8 @@ winrt::com_ptr D3DCaptureState::CopyFrameToCPU(const winrt::com
desc.BindFlags = 0;
winrt::com_ptr cpuTexture;
- winrt::check_hresult(d3dState->d3dDevice->CreateTexture2D(&desc, nullptr, cpuTexture.put()));
- context->CopyResource(cpuTexture.get(), frameTexture.get());
+ winrt::check_hresult(dxgiAPI->d3dForCapture.d3dDevice->CreateTexture2D(&desc, nullptr, cpuTexture.put()));
+ dxgiAPI->d3dForCapture.d3dContext->CopyResource(cpuTexture.get(), frameTexture.get());
return cpuTexture;
}
@@ -142,15 +117,25 @@ auto GetDXGIInterfaceFromObject(winrt::IInspectable const& object)
void D3DCaptureState::OnFrameArrived(const winrt::Direct3D11CaptureFramePool& sender, const winrt::IInspectable&)
{
// Prevent calling a callback on a partially destroyed state
- std::unique_lock callbackLock{ destructorMutex };
+ std::lock_guard callbackLock{ frameArrivedMutex };
bool resized = false;
POINT cursorPos = {};
GetCursorPos(&cursorPos);
- auto frame = sender.TryGetNextFrame();
- winrt::check_bool(frame);
- if (monitorArea.inside(cursorPos) || captureOutsideOfMonitor)
+ winrt::Direct3D11CaptureFrame frame = nullptr;
+ try
+ {
+ frame = sender.TryGetNextFrame();
+ }
+ catch (...)
+ {
+ }
+
+ if (!frame)
+ return;
+
+ if (monitorArea.inside(cursorPos) || !continuousCapture)
{
winrt::com_ptr texture;
{
@@ -170,7 +155,10 @@ void D3DCaptureState::OnFrameArrived(const winrt::Direct3D11CaptureFramePool& se
auto gpuTexture = GetDXGIInterfaceFromObject(surface);
texture = CopyFrameToCPU(gpuTexture);
surface.Close();
- MappedTextureView textureView{ texture, context, static_cast(frameSize.Width), static_cast(frameSize.Height) };
+ MappedTextureView textureView{ texture,
+ dxgiAPI->d3dForCapture.d3dContext,
+ static_cast(frameSize.Width),
+ static_cast(frameSize.Height) };
frameCallback(std::move(textureView));
}
@@ -178,69 +166,60 @@ void D3DCaptureState::OnFrameArrived(const winrt::Direct3D11CaptureFramePool& se
frame.Close();
- DXGI_PRESENT_PARAMETERS presentParameters = {};
- swapChain->Present1(1, 0, &presentParameters);
-
if (resized)
{
framePool.Recreate(device, pixelFormat, 2, frameSize);
}
}
-std::unique_ptr D3DCaptureState::Create(D3DState* d3dState,
- winrt::GraphicsCaptureItem item,
+std::unique_ptr D3DCaptureState::Create(DxgiAPI* dxgiAPI,
+ MonitorInfo monitorInfo,
const winrt::DirectXPixelFormat pixelFormat,
- Box monitorArea,
- const bool captureOutsideOfMonitor)
+ const bool continuousCapture)
{
- std::lock_guard guard{ gpuAccessLock };
-
+ const auto dims = monitorInfo.GetScreenSize(true);
const DXGI_SWAP_CHAIN_DESC1 desc = {
- .Width = static_cast(item.Size().Width),
- .Height = static_cast(item.Size().Height),
+ .Width = static_cast(dims.width()),
+ .Height = static_cast(dims.height()),
.Format = static_cast(pixelFormat),
.SampleDesc = { .Count = 1, .Quality = 0 },
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = 2,
.Scaling = DXGI_SCALING_STRETCH,
- .SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL,
+ .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD,
.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED,
};
- winrt::com_ptr adapter;
- winrt::check_hresult(d3dState->dxgiDevice->GetParent(winrt::guid_of(), adapter.put_void()));
- winrt::com_ptr factory;
- winrt::check_hresult(adapter->GetParent(winrt::guid_of(), factory.put_void()));
winrt::com_ptr swapChain;
- winrt::check_hresult(factory->CreateSwapChainForComposition(d3dState->d3dDevice.get(), &desc, nullptr, swapChain.put()));
-
- winrt::com_ptr context;
- d3dState->d3dDevice->GetImmediateContext(context.put());
- winrt::check_bool(context);
- auto contextMultithread = context.as();
- contextMultithread->SetMultithreadProtected(true);
+ winrt::check_hresult(dxgiAPI->d3dForCapture.dxgiFactory2->CreateSwapChainForComposition(dxgiAPI->d3dForCapture.d3dDevice.get(),
+ &desc,
+ nullptr,
+ swapChain.put()));
// We must create the object in a heap, since we need to pin it in memory to receive callbacks
- auto statePtr = new D3DCaptureState{ d3dState,
+ auto statePtr = new D3DCaptureState{ dxgiAPI,
std::move(swapChain),
- std::move(context),
- item,
pixelFormat,
- monitorArea,
- captureOutsideOfMonitor };
+ std::move(monitorInfo),
+ continuousCapture };
return std::unique_ptr{ statePtr };
}
D3DCaptureState::~D3DCaptureState()
{
- std::unique_lock callbackLock{ destructorMutex };
+ std::unique_lock callbackLock{ frameArrivedMutex };
StopCapture();
- framePool.Close();
}
void D3DCaptureState::StartSessionInPreferredMode()
{
+ auto item = CreateCaptureItemForMonitor(monitor);
+ frameSize = item.Size();
+ framePool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(device, pixelFormat, 2, item.Size());
+ session = framePool.CreateCaptureSession(item);
+ framePool.FrameArrived({ this, &D3DCaptureState::OnFrameArrived });
+
// Try disable border if possible (available on Windows ver >= 20348)
if (auto session3 = session.try_as())
{
@@ -270,27 +249,35 @@ MappedTextureView D3DCaptureState::CaptureSingleFrame()
result.emplace(std::move(tex));
frameArrivedEvent.SetEvent();
};
- std::lock_guard guard{ gpuAccessLock };
StartSessionInPreferredMode();
frameArrivedEvent.wait();
- assert(result.has_value());
return std::move(*result);
}
void D3DCaptureState::StopCapture()
{
- session.Close();
+ try
+ {
+ if (session)
+ session.Close();
+
+ if (framePool)
+ framePool.Close();
+ }
+ catch (...)
+ {
+ // RPC call might fail here
+ }
}
void UpdateCaptureState(const CommonState& commonState,
Serialized& state,
HWND window,
- const MappedTextureView& textureView,
- const bool continuousCapture)
+ const MappedTextureView& textureView)
{
- const auto cursorPos = convert::FromSystemToRelative(window, commonState.cursorPosSystemSpace);
+ const auto cursorPos = convert::FromSystemToWindow(window, commonState.cursorPosSystemSpace);
const bool cursorInLeftScreenHalf = cursorPos.x < textureView.view.width / 2;
const bool cursorInTopScreenHalf = cursorPos.y < textureView.view.height / 2;
uint8_t pixelTolerance = {};
@@ -310,8 +297,7 @@ void UpdateCaptureState(const CommonState& commonState,
const RECT bounds = DetectEdges(textureView.view,
cursorPos,
perColorChannelEdgeDetection,
- pixelTolerance,
- continuousCapture);
+ pixelTolerance);
#if defined(DEBUG_EDGES)
char buffer[256];
@@ -328,50 +314,54 @@ void UpdateCaptureState(const CommonState& commonState,
OutputDebugStringA(buffer);
#endif
state.Access([&](MeasureToolState& state) {
- state.perScreen[window].measuredEdges = bounds;
+ state.perScreen[window].measuredEdges = Measurement{ bounds };
});
}
-std::thread StartCapturingThread(D3DState* d3dState,
+std::thread StartCapturingThread(DxgiAPI* dxgiAPI,
const CommonState& commonState,
Serialized& state,
HWND window,
MonitorInfo monitor)
{
- return SpawnLoggedThread(L"Screen Capture thread", [&state, &commonState, monitor, window, d3dState] {
- auto captureInterop = winrt::get_activation_factory<
- winrt::GraphicsCaptureItem,
- IGraphicsCaptureItemInterop>();
-
- winrt::GraphicsCaptureItem item = nullptr;
-
- winrt::check_hresult(captureInterop->CreateForMonitor(
- monitor.GetHandle(),
- winrt::guid_of(),
- winrt::put_abi(item)));
-
+ return SpawnLoggedThread(L"Screen Capture thread", [&state, &commonState, monitor, window, dxgiAPI] {
bool continuousCapture = {};
state.Read([&](const MeasureToolState& state) {
continuousCapture = state.global.continuousCapture;
});
- const auto monitorArea = monitor.GetScreenSize(true);
- auto captureState = D3DCaptureState::Create(d3dState,
- item,
+ auto captureState = D3DCaptureState::Create(dxgiAPI,
+ monitor,
winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized,
- monitorArea,
- !continuousCapture);
+ continuousCapture);
+ const auto monitorArea = monitor.GetScreenSize(true);
+ bool mouseOnMonitor = false;
if (continuousCapture)
{
- captureState->StartCapture([&, window](MappedTextureView textureView) {
- UpdateCaptureState(commonState, state, window, textureView, continuousCapture);
- });
-
while (IsWindow(window) && !commonState.closeOnOtherMonitors)
{
- std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION);
+ if (mouseOnMonitor == monitorArea.inside(commonState.cursorPosSystemSpace))
+ {
+ std::this_thread::sleep_for(consts::TARGET_FRAME_DURATION);
+ continue;
+ }
+
+ mouseOnMonitor = !mouseOnMonitor;
+ if (mouseOnMonitor)
+ {
+ captureState->StartCapture([&, window](MappedTextureView textureView) {
+ UpdateCaptureState(commonState, state, window, textureView);
+ });
+ }
+ else
+ {
+ state.Access([&](MeasureToolState& state) {
+ state.perScreen[window].measuredEdges = {};
+ });
+
+ captureState->StopCapture();
+ }
}
- captureState->StopCapture();
}
else
{
@@ -394,7 +384,15 @@ std::thread StartCapturingThread(D3DState* d3dState,
auto path = std::filesystem::temp_directory_path() / buf;
textureView.view.SaveAsBitmap(path.string().c_str());
#endif
- UpdateCaptureState(commonState, state, window, textureView, continuousCapture);
+ UpdateCaptureState(commonState, state, window, textureView);
+ mouseOnMonitor = true;
+ }
+ else if (mouseOnMonitor)
+ {
+ state.Access([&](MeasureToolState& state) {
+ state.perScreen[window].measuredEdges = {};
+ });
+ mouseOnMonitor = false;
}
const auto frameTime = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - now);
@@ -404,5 +402,7 @@ std::thread StartCapturingThread(D3DState* d3dState,
}
}
}
+
+ captureState->StopCapture();
});
}
diff --git a/src/modules/MeasureTool/MeasureToolCore/ScreenCapturing.h b/src/modules/MeasureTool/MeasureToolCore/ScreenCapturing.h
index 8cb7e912e63..4ae3554125a 100644
--- a/src/modules/MeasureTool/MeasureToolCore/ScreenCapturing.h
+++ b/src/modules/MeasureTool/MeasureToolCore/ScreenCapturing.h
@@ -1,19 +1,11 @@
#pragma once
+#include "DxgiAPI.h"
#include "ToolState.h"
#include
-struct D3DState
-{
- winrt::com_ptr d3dDevice;
- winrt::com_ptr dxgiDevice;
- winrt::com_ptr d3dDeviceInspectable;
-
- D3DState();
-};
-
-std::thread StartCapturingThread(D3DState* d3dState,
+std::thread StartCapturingThread(DxgiAPI* dxgiAPI,
const CommonState& commonState,
Serialized& state,
HWND targetWindow,
diff --git a/src/modules/MeasureTool/MeasureToolCore/Settings.cpp b/src/modules/MeasureTool/MeasureToolCore/Settings.cpp
index 22c809bec81..418b6d1a957 100644
--- a/src/modules/MeasureTool/MeasureToolCore/Settings.cpp
+++ b/src/modules/MeasureTool/MeasureToolCore/Settings.cpp
@@ -15,6 +15,7 @@ namespace
const wchar_t JSON_KEY_PIXEL_TOLERANCE[] = L"PixelTolerance";
const wchar_t JSON_KEY_PER_COLOR_CHANNEL_EDGE_DETECTION[] = L"PerColorChannelEdgeDetection";
const wchar_t JSON_KEY_MEASURE_CROSS_COLOR[] = L"MeasureCrossColor";
+ const wchar_t JSON_KEY_UNITS_OF_MEASURE[] = L"UnitsOfMeasure";
}
Settings Settings::LoadFromFile()
@@ -65,6 +66,14 @@ Settings Settings::LoadFromFile()
catch (...)
{
}
+
+ try
+ {
+ result.units = static_cast(props.GetNamedObject(JSON_KEY_UNITS_OF_MEASURE).GetNamedNumber(JSON_KEY_VALUE));
+ }
+ catch (...)
+ {
+ }
}
catch (...)
{
diff --git a/src/modules/MeasureTool/MeasureToolCore/Settings.h b/src/modules/MeasureTool/MeasureToolCore/Settings.h
index 75a397abc2e..76f3af7ea24 100644
--- a/src/modules/MeasureTool/MeasureToolCore/Settings.h
+++ b/src/modules/MeasureTool/MeasureToolCore/Settings.h
@@ -3,6 +3,8 @@
#include
#include
+#include "Measurement.h"
+
struct Settings
{
uint8_t pixelTolerance = 30;
@@ -10,6 +12,7 @@ struct Settings
bool drawFeetOnCross = true;
bool perColorChannelEdgeDetection = false;
std::array lineColor = {255, 69, 0};
+ Measurement::Unit units = Measurement::Unit::Pixel;
static Settings LoadFromFile();
};
\ No newline at end of file
diff --git a/src/modules/MeasureTool/MeasureToolCore/ToolState.h b/src/modules/MeasureTool/MeasureToolCore/ToolState.h
index e5222892f07..b4e14a322b8 100644
--- a/src/modules/MeasureTool/MeasureToolCore/ToolState.h
+++ b/src/modules/MeasureTool/MeasureToolCore/ToolState.h
@@ -16,10 +16,11 @@
//#define DEBUG_OVERLAY
#include "BGRATextureView.h"
+#include "Measurement.h"
struct OverlayBoxText
{
- std::array buffer = {};
+ std::array buffer = {};
};
struct CommonState
@@ -28,6 +29,8 @@ struct CommonState
D2D1::ColorF lineColor = D2D1::ColorF::OrangeRed;
Box toolbarBoundingBox;
+ Measurement::Unit units = Measurement::Unit::Pixel;
+
mutable Serialized overlayBoxText;
POINT cursorPosSystemSpace = {}; // updated atomically
std::atomic_bool closeOnOtherMonitors = false;
@@ -38,7 +41,7 @@ struct BoundsToolState
struct PerScreen
{
std::optional currentRegionStart;
- std::vector measurements;
+ std::vector measurements;
};
std::unordered_map perScreen;
@@ -67,7 +70,7 @@ struct MeasureToolState
{
bool cursorInLeftScreenHalf = false;
bool cursorInTopScreenHalf = false;
- RECT measuredEdges = {};
+ std::optional measuredEdges;
// While not in a continuous capturing mode, we need to draw captured backgrounds. These are passed
// directly from a capturing thread.
const MappedTextureView* capturedScreenTexture = nullptr;
@@ -79,6 +82,3 @@ struct MeasureToolState
CommonState* commonState = nullptr; // required for WndProc
};
-
-// Concurrently accessing Direct2D and Direct3D APIs make the driver go boom
-extern std::recursive_mutex gpuAccessLock;
\ No newline at end of file
diff --git a/src/modules/MeasureTool/MeasureToolCore/constants.h b/src/modules/MeasureTool/MeasureToolCore/constants.h
index dbe7ab3dbde..36027140345 100644
--- a/src/modules/MeasureTool/MeasureToolCore/constants.h
+++ b/src/modules/MeasureTool/MeasureToolCore/constants.h
@@ -4,12 +4,11 @@
namespace consts
{
- constexpr inline size_t TARGET_FRAME_RATE = 120;
- constexpr inline auto TARGET_FRAME_DURATION = std::chrono::milliseconds{ 1000 } / TARGET_FRAME_RATE;
+ constexpr inline size_t TARGET_FRAME_RATE = 90;
+ constexpr inline auto TARGET_FRAME_DURATION = std::chrono::microseconds{ 1000000 } / TARGET_FRAME_RATE;
constexpr inline float FONT_SIZE = 14.f;
constexpr inline float TEXT_BOX_CORNER_RADIUS = 4.f;
- constexpr inline float TEXT_BOX_MARGIN_COEFF = 1.25f;
constexpr inline float FEET_HALF_LENGTH = 2.f;
constexpr inline float SHADOW_OPACITY = .4f;
constexpr inline float SHADOW_RADIUS = 6.f;
diff --git a/src/modules/MeasureTool/MeasureToolCore/pch.h b/src/modules/MeasureTool/MeasureToolCore/pch.h
index f2700373e2b..67ca0f9d8cd 100644
--- a/src/modules/MeasureTool/MeasureToolCore/pch.h
+++ b/src/modules/MeasureTool/MeasureToolCore/pch.h
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
#include
#include