[AlwaysOnTop] Improve performance and power consumption (#15993)

This commit is contained in:
Andrey Nekrasov 2022-02-03 20:30:05 +03:00 committed by GitHub
parent ddf96e28b8
commit 3e7b04891d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 114 deletions

View File

@ -488,6 +488,7 @@ DQTYPE
DRAWFRAME
drawingcolor
dreamsofameaningfullife
DRect
drivedetectionwarning
dshow
dst
@ -1194,6 +1195,7 @@ mdpreviewhandler
MEDIASUBTYPE
mediatype
Melman
memcmp
memcpy
memset
MENUITEMINFO
@ -2103,6 +2105,7 @@ uap
udit
Udp
uefi
UHash
UIA
Uid
uint

View File

@ -1,6 +1,18 @@
#include "pch.h"
#include "FrameDrawer.h"
namespace
{
size_t D2DRectUHash(D2D1_SIZE_U rect)
{
using pod_repr_t = uint64_t;
static_assert(sizeof(D2D1_SIZE_U) == sizeof(pod_repr_t));
std::hash<pod_repr_t> hasher{};
return hasher(*reinterpret_cast<const pod_repr_t*>(&rect));
}
}
std::unique_ptr<FrameDrawer> FrameDrawer::Create(HWND window)
{
auto self = std::make_unique<FrameDrawer>(window);
@ -12,63 +24,55 @@ std::unique_ptr<FrameDrawer> FrameDrawer::Create(HWND window)
return nullptr;
}
FrameDrawer::FrameDrawer(FrameDrawer&& other) :
m_window(other.m_window),
m_renderTarget(std::move(other.m_renderTarget)),
m_sceneRect(std::move(other.m_sceneRect)),
m_renderThread(std::move(m_renderThread))
{
}
FrameDrawer::FrameDrawer(HWND window) :
m_window(window), m_renderTarget(nullptr)
m_window(window)
{
}
FrameDrawer::~FrameDrawer()
bool FrameDrawer::CreateRenderTargets(const RECT& clientRect)
{
m_abortThread = true;
m_renderThread.join();
HRESULT hr;
if (m_renderTarget)
constexpr float DPI = 96.f; // Always using the default in DPI-aware mode
const auto renderTargetProperties = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED),
DPI,
DPI);
const auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
const auto rectHash = D2DRectUHash(renderTargetSize);
if (m_renderTarget && rectHash == m_renderTargetSizeHash)
{
m_renderTarget->Release();
// Already at the desired size -> do nothing
return true;
}
m_renderTarget = nullptr;
const auto hwndRenderTargetProperties = D2D1::HwndRenderTargetProperties(m_window, renderTargetSize, D2D1_PRESENT_OPTIONS_NONE);
hr = GetD2DFactory()->CreateHwndRenderTarget(renderTargetProperties, hwndRenderTargetProperties, m_renderTarget.put());
if (!SUCCEEDED(hr) || !m_renderTarget)
{
return false;
}
m_renderTargetSizeHash = rectHash;
return true;
}
bool FrameDrawer::Init()
{
RECT clientRect;
// Obtain the size of the drawing area.
if (!GetClientRect(m_window, &clientRect))
{
return false;
}
HRESULT hr;
// Create a Direct2D render target
// We should always use the DPI value of 96 since we're running in DPI aware mode
auto renderTargetProperties = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_DEFAULT,
D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED),
96.f,
96.f);
auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
auto hwndRenderTargetProperties = D2D1::HwndRenderTargetProperties(m_window, renderTargetSize);
hr = GetD2DFactory()->CreateHwndRenderTarget(renderTargetProperties, hwndRenderTargetProperties, &m_renderTarget);
if (!SUCCEEDED(hr))
{
return false;
}
m_renderThread = std::thread([this]() { RenderLoop(); });
return true;
return CreateRenderTargets(clientRect);
}
void FrameDrawer::Hide()
@ -79,19 +83,64 @@ void FrameDrawer::Hide()
void FrameDrawer::Show()
{
ShowWindow(m_window, SW_SHOWNA);
Render();
}
void FrameDrawer::SetBorderRect(RECT windowRect, COLORREF color, float thickness)
{
std::unique_lock lock(m_mutex);
auto borderColor = ConvertColor(color);
m_sceneRect = DrawableRect{
const auto newSceneRect = DrawableRect{
.rect = ConvertRect(windowRect),
.borderColor = borderColor,
.borderColor = ConvertColor(color),
.thickness = thickness
};
const bool colorUpdated = std::memcmp(&m_sceneRect.borderColor, &newSceneRect.borderColor, sizeof(newSceneRect.borderColor));
const bool thicknessUpdated = m_sceneRect.thickness != newSceneRect.thickness;
const bool needsRedraw = colorUpdated || thicknessUpdated;
RECT clientRect;
if (!GetClientRect(m_window, &clientRect))
{
return;
}
m_sceneRect = newSceneRect;
const auto renderTargetSize = D2D1::SizeU(clientRect.right - clientRect.left, clientRect.bottom - clientRect.top);
const auto rectHash = D2DRectUHash(renderTargetSize);
const bool atTheDesiredSize = (rectHash == m_renderTargetSizeHash) && m_renderTarget;
if (!atTheDesiredSize)
{
const bool resizeOk = m_renderTarget && SUCCEEDED(m_renderTarget->Resize(renderTargetSize));
if (!resizeOk)
{
if (!CreateRenderTargets(clientRect))
{
Logger::error(L"Failed to create render targets");
}
}
else
{
m_renderTargetSizeHash = rectHash;
}
}
if (colorUpdated)
{
m_borderBrush = nullptr;
if (m_renderTarget)
{
m_renderTarget->CreateSolidColorBrush(m_sceneRect.borderColor, m_borderBrush.put());
}
}
if (!atTheDesiredSize || needsRedraw)
{
Render();
}
}
ID2D1Factory* FrameDrawer::GetD2DFactory()
@ -127,46 +176,18 @@ D2D1_RECT_F FrameDrawer::ConvertRect(RECT rect)
return D2D1::RectF((float)rect.left, (float)rect.top, (float)rect.right, (float)rect.bottom);
}
FrameDrawer::RenderResult FrameDrawer::Render()
void FrameDrawer::Render()
{
std::unique_lock lock(m_mutex);
if (!m_renderTarget)
{
return RenderResult::Failed;
}
return;
m_renderTarget->BeginDraw();
// Draw backdrop
m_renderTarget->Clear(D2D1::ColorF(0.f, 0.f, 0.f, 0.f));
ID2D1SolidColorBrush* borderBrush = nullptr;
m_renderTarget->CreateSolidColorBrush(m_sceneRect.borderColor, &borderBrush);
if (borderBrush)
if (m_borderBrush)
{
m_renderTarget->DrawRectangle(m_sceneRect.rect, borderBrush, m_sceneRect.thickness);
borderBrush->Release();
m_renderTarget->DrawRectangle(m_sceneRect.rect, m_borderBrush.get(), m_sceneRect.thickness);
}
// The lock must be released here, as EndDraw() will wait for vertical sync
lock.unlock();
m_renderTarget->EndDraw();
return RenderResult::Ok;
}
void FrameDrawer::RenderLoop()
{
while (!m_abortThread)
{
auto result = Render();
if (result == RenderResult::Failed)
{
Logger::error("Render failed");
Hide();
m_abortThread = true;
}
}
}
}

View File

@ -1,7 +1,9 @@
#pragma once
#include <mutex>
#include <d2d1.h>
#include <winrt/base.h>
#include <dwrite.h>
class FrameDrawer
@ -10,8 +12,7 @@ public:
static std::unique_ptr<FrameDrawer> Create(HWND window);
FrameDrawer(HWND window);
FrameDrawer(FrameDrawer&& other);
~FrameDrawer();
FrameDrawer(FrameDrawer&& other) = default;
bool Init();
@ -20,6 +21,8 @@ public:
void SetBorderRect(RECT windowRect, COLORREF color, float thickness);
private:
bool CreateRenderTargets(const RECT& clientRect);
struct DrawableRect
{
D2D1_RECT_F rect;
@ -27,25 +30,15 @@ private:
float thickness;
};
enum struct RenderResult
{
Ok,
Failed,
};
static ID2D1Factory* GetD2DFactory();
static IDWriteFactory* GetWriteFactory();
static D2D1_COLOR_F ConvertColor(COLORREF color);
static D2D1_RECT_F ConvertRect(RECT rect);
RenderResult Render();
void RenderLoop();
void Render();
HWND m_window = nullptr;
ID2D1HwndRenderTarget* m_renderTarget = nullptr;
std::mutex m_mutex;
DrawableRect m_sceneRect;
std::atomic<bool> m_abortThread = false;
std::thread m_renderThread;
size_t m_renderTargetSizeHash = {};
winrt::com_ptr<ID2D1HwndRenderTarget> m_renderTarget;
winrt::com_ptr<ID2D1SolidColorBrush> m_borderBrush;
DrawableRect m_sceneRect = {};
};

View File

@ -31,18 +31,10 @@ std::optional<RECT> GetFrameRect(HWND window)
}
WindowBorder::WindowBorder(HWND window) :
SettingsObserver({SettingId::FrameColor, SettingId::FrameThickness, SettingId::FrameAccentColor }),
m_window(nullptr),
m_trackingWindow(window),
m_frameDrawer(nullptr)
{
}
WindowBorder::WindowBorder(WindowBorder&& other) :
SettingsObserver({ SettingId::FrameColor, SettingId::FrameThickness, SettingId::FrameAccentColor }),
m_window(other.m_window),
m_trackingWindow(other.m_trackingWindow),
m_frameDrawer(std::move(other.m_frameDrawer))
m_window(nullptr),
m_trackingWindow(window),
m_frameDrawer(nullptr)
{
}
@ -72,6 +64,12 @@ std::unique_ptr<WindowBorder> WindowBorder::Create(HWND window, HINSTANCE hinsta
return nullptr;
}
namespace
{
constexpr uint32_t REFRESH_BORDER_TIMER_ID = 123;
constexpr uint32_t REFRESH_BORDER_INTERVAL = 100;
}
bool WindowBorder::Init(HINSTANCE hinstance)
{
if (!m_trackingWindow)
@ -136,6 +134,8 @@ bool WindowBorder::Init(HINSTANCE hinstance)
UpdateBorderProperties();
m_frameDrawer->Show();
m_timer_id = SetTimer(m_window, REFRESH_BORDER_TIMER_ID, REFRESH_BORDER_INTERVAL, nullptr);
return true;
}
@ -154,7 +154,7 @@ void WindowBorder::UpdateBorderPosition() const
}
RECT rect = rectOpt.value();
SetWindowPos(m_window, m_trackingWindow, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOREDRAW);
SetWindowPos(m_window, m_trackingWindow, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOREDRAW | SWP_NOACTIVATE);
}
void WindowBorder::UpdateBorderProperties() const
@ -170,7 +170,9 @@ void WindowBorder::UpdateBorderProperties() const
return;
}
RECT windowRect = windowRectOpt.value();
const RECT windowRect = windowRectOpt.value();
SetWindowPos(m_window, m_trackingWindow, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, SWP_NOREDRAW | SWP_NOACTIVATE);
RECT frameRect{ 0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top };
COLORREF color;
@ -192,8 +194,22 @@ LRESULT WindowBorder::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexce
{
switch (message)
{
case WM_TIMER:
{
switch (wparam)
{
case REFRESH_BORDER_TIMER_ID:
KillTimer(m_window, m_timer_id);
m_timer_id = SetTimer(m_window, REFRESH_BORDER_TIMER_ID, REFRESH_BORDER_INTERVAL, nullptr);
UpdateBorderPosition();
UpdateBorderProperties();
break;
}
break;
}
case WM_NCDESTROY:
{
KillTimer(m_window, m_timer_id);
::DefWindowProc(m_window, message, wparam, lparam);
SetWindowLongPtr(m_window, GWLP_USERDATA, 0);
}
@ -231,7 +247,7 @@ void WindowBorder::SettingsUpdate(SettingId id)
UpdateBorderProperties();
}
break;
case SettingId::FrameColor:
{
UpdateBorderProperties();
@ -246,5 +262,4 @@ void WindowBorder::SettingsUpdate(SettingId id)
default:
break;
}
}

View File

@ -7,8 +7,8 @@ class FrameDrawer;
class WindowBorder : public SettingsObserver
{
WindowBorder(HWND window);
WindowBorder(WindowBorder&& other);
WindowBorder(WindowBorder&& other) = default;
public:
static std::unique_ptr<WindowBorder> Create(HWND window, HINSTANCE hinstance);
~WindowBorder();
@ -32,8 +32,9 @@ protected:
}
private:
HWND m_window;
HWND m_trackingWindow;
UINT_PTR m_timer_id = {};
HWND m_window = {};
HWND m_trackingWindow = {};
std::unique_ptr<FrameDrawer> m_frameDrawer;
LRESULT WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept;

View File

@ -4,4 +4,7 @@
#include <winrt/base.h>
#include <wil/resource.h>
#include <ProjectTelemetry.h>
#include <common/logger/logger.h>
#include <common/logger/logger.h>
#include <functional>
#include <array>