Skip to content

Commit

Permalink
Change from GDI+ to Direct2D
Browse files Browse the repository at this point in the history
- Gained massive performance improvements
  - Images in one of my test sets used to take around 45ms and now around 500us, never even close to 1ms
  • Loading branch information
visuve committed Jan 7, 2024
1 parent 0d405ff commit 46177b1
Show file tree
Hide file tree
Showing 17 changed files with 297 additions and 355 deletions.
137 changes: 96 additions & 41 deletions PictureBrowser/CanvasWidget.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
#include "PCH.hpp"
#include "CanvasWidget.hpp"
#include "GdiExtensions.hpp"
#include "Resource.h"
#include "LogWrap.hpp"

namespace PictureBrowser
{
constexpr D2D_RECT_F ScaleAndCenterTo(const D2D_SIZE_F& canvas, const D2D_SIZE_F& image)
{
float aspectRatio = std::min(
canvas.width / image.width,
canvas.height / image.height);

float w = image.width * aspectRatio;
float h = image.height * aspectRatio;

float x = (canvas.width - w) / 2.0f;
float y = (canvas.height - h) / 2.0f;

return { x, y, w + x, h + y };
}

constexpr void Zoom(D2D_RECT_F& rect, float zoomPercent)
{
if (zoomPercent > 0)
{
float width = rect.right - rect.left;
float height = rect.bottom - rect.top;
float scale = zoomPercent / 100.0f;

rect.left -= width * scale;
rect.top -= height * scale;
rect.right += width * scale;
rect.bottom += height * scale;
}
}

CanvasWidget::CanvasWidget(HINSTANCE instance, HWND parent, const std::shared_ptr<ImageCache>& imageCache) :
Widget(
0,
WC_STATIC,
nullptr,
ClassName(CanvasWidget),
WS_VISIBLE | WS_CHILD | WS_BORDER,
CW_USEDEFAULT,
CW_USEDEFAULT,
Expand All @@ -22,6 +51,33 @@ namespace PictureBrowser
nullptr),
_imageCache(imageCache)
{
HRESULT hr;

hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, _factory.GetAddressOf());

if (FAILED(hr))
{
std::unreachable();
}

RECT rect = ClientRect();

D2D1_SIZE_U size = D2D1::SizeU(rect.right, rect.bottom);

auto rtp = D2D1::RenderTargetProperties();
auto hrtp = D2D1::HwndRenderTargetProperties(_window, size);

hr = _factory->CreateHwndRenderTarget(rtp, hrtp, &_renderTarget);

if (FAILED(hr))
{
std::unreachable();
}

// TODO: I really do not like this, but I could not come up with else
_imageCache->SetRenderTarget(_renderTarget.Get());

_renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::DarkGray), &_brush);
}

void CanvasWidget::HandleMessage(UINT message, WPARAM wParam, LPARAM lParam)
Expand Down Expand Up @@ -71,61 +127,59 @@ namespace PictureBrowser

void CanvasWidget::Resize()
{
if (!_renderTarget)
{
return;
}

SIZE size = ClientSize();

_buffer = std::make_unique<Gdiplus::Bitmap>(size.cx, size.cy);
_graphics = std::make_unique<Gdiplus::Graphics>(_buffer.get());
_renderTarget->Resize(D2D1::SizeU(size.cx, size.cy));
}

void CanvasWidget::OnPaint() const
{
const auto start = std::chrono::high_resolution_clock::now();

GdiExtensions::ContextWrapper context(_window);

if (!context.IsValid())
{
std::unreachable();
}

if (!_buffer || !_graphics)
{
std::unreachable();
}
PAINTSTRUCT ps;
BeginPaint(_window, &ps);

const INT width = _buffer->GetWidth();
const INT height = _buffer->GetHeight();
_renderTarget->BeginDraw();

const Gdiplus::SolidBrush grayBrush(static_cast<Gdiplus::ARGB>(Gdiplus::Color::DarkGray));
_graphics->FillRectangle(&grayBrush, 0, 0, width, height);
ComPtr<ID2D1Bitmap> bitmap = _imageCache->Current();

std::shared_ptr<Gdiplus::Bitmap> bitmap = _imageCache->Current();
_renderTarget->Clear(bitmap ? D2D1::ColorF(D2D1::ColorF::Gray) : D2D1::ColorF(D2D1::ColorF::Red));

if (bitmap)
{
Gdiplus::Size canvasSize(width, height);
Gdiplus::Size imageSize(bitmap->GetWidth(), bitmap->GetHeight());
Gdiplus::Rect scaled;
D2D_SIZE_F canvasSize = _renderTarget->GetSize();
D2D_SIZE_F imageSize = bitmap->GetSize();
D2D_RECT_F scaled = ScaleAndCenterTo(canvasSize, imageSize);

scaled.left += _mouseDragOffset.x;
scaled.top += _mouseDragOffset.y;
scaled.right += _mouseDragOffset.x;
scaled.bottom += _mouseDragOffset.y;

GdiExtensions::ScaleAndCenterTo(canvasSize, imageSize, scaled);
GdiExtensions::Zoom(scaled, _zoomPercent);
scaled.Offset(_mouseDragOffset);
Zoom(scaled, _zoomPercent);

if (_isDragging)
{
const Gdiplus::Pen pen(static_cast<Gdiplus::ARGB>(Gdiplus::Color::Gray), 2.0f);
_graphics->DrawRectangle(&pen, scaled);
_renderTarget->FillRectangle(scaled, _brush.Get());
}
else
{
_graphics->DrawImage(bitmap.get(), scaled);
_renderTarget->DrawBitmap(bitmap.Get(), scaled);
}
}

context.Graphics().DrawImage(_buffer.get(), 0, 0, width, height);

_renderTarget->EndDraw();

EndPaint(_window, &ps);

const auto diff = std::chrono::high_resolution_clock::now() - start;
LOGD << std::chrono::duration_cast<std::chrono::milliseconds>(diff).count() << L"ms";
LOGD << std::chrono::duration_cast<std::chrono::microseconds>(diff).count() << L"us";
}

void CanvasWidget::Invalidate() const
Expand All @@ -142,9 +196,9 @@ namespace PictureBrowser
}
}

void CanvasWidget::OnImageChanged(std::filesystem::path path)
void CanvasWidget::OnImageChanged(const std::filesystem::path& path)
{
_zoomPercent = 0;
_zoomPercent = 0.0f;

ZeroInit(&_mouseDragStart);
ZeroInit(&_mouseDragOffset);
Expand All @@ -161,16 +215,16 @@ namespace PictureBrowser
switch (wParam)
{
case VK_OEM_MINUS:
if (_zoomPercent > 0)
if (_zoomPercent > 0.0f)
{
_zoomPercent -= 5;
_zoomPercent -= 5.0f;
Invalidate();
}
break;
case VK_OEM_PLUS:
if (_zoomPercent < 1000)
if (_zoomPercent < 1000.0f)
{
_zoomPercent += 5;
_zoomPercent += 5.0f;
Invalidate();
}
break;
Expand All @@ -189,8 +243,8 @@ namespace PictureBrowser
return;
}

_mouseDragStart.X = point.x - _mouseDragOffset.X;
_mouseDragStart.Y = point.y - _mouseDragOffset.Y;
_mouseDragStart.x = point.x - _mouseDragOffset.x;
_mouseDragStart.y = point.y - _mouseDragOffset.y;
}

void CanvasWidget::OnMouseMove(LPARAM lParam)
Expand All @@ -208,9 +262,10 @@ namespace PictureBrowser

bool CanvasWidget::UpdateMousePosition(LPARAM lParam)
{
Gdiplus::Point distance(LOWORD(lParam) - _mouseDragStart.X, HIWORD(lParam) - _mouseDragStart.Y);
D2D_POINT_2F distance(LOWORD(lParam) - _mouseDragStart.x, HIWORD(lParam) - _mouseDragStart.y);

if (distance.Equals(_mouseDragOffset))
if (distance.x == _mouseDragOffset.x &&
distance.y == _mouseDragOffset.y)
{
return false;
}
Expand Down
16 changes: 9 additions & 7 deletions PictureBrowser/CanvasWidget.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ namespace PictureBrowser
{
public:
CanvasWidget(
HINSTANCE instance,
HINSTANCE instance,
HWND parent,
const std::shared_ptr<ImageCache>& imageCache);

void HandleMessage(UINT, WPARAM, LPARAM) override;

void OnImageChanged(std::filesystem::path path);
void OnImageChanged(const std::filesystem::path& path);

void Resize();

Expand All @@ -29,13 +29,15 @@ namespace PictureBrowser
bool UpdateMousePosition(LPARAM);
void OnLeftMouseUp(LPARAM);

int _zoomPercent = 0;
float _zoomPercent = 0.0f;
bool _isDragging = false;
Gdiplus::Point _mouseDragStart;
Gdiplus::Point _mouseDragOffset;
D2D_POINT_2F _mouseDragStart = {};
D2D_POINT_2F _mouseDragOffset = {};
std::shared_ptr<ImageCache> _imageCache;
std::unique_ptr<Gdiplus::Image> _buffer;
std::unique_ptr<Gdiplus::Graphics> _graphics;

ComPtr<ID2D1Factory> _factory;
ComPtr<ID2D1HwndRenderTarget> _renderTarget;
ComPtr<ID2D1SolidColorBrush> _brush;
};
}

2 changes: 1 addition & 1 deletion PictureBrowser/FileListWidget.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include "ImageCache.hpp"
#include "Window.hpp"
#include "Widget.hpp"

namespace PictureBrowser
{
Expand Down
118 changes: 0 additions & 118 deletions PictureBrowser/GdiExtensions.cpp

This file was deleted.

Loading

0 comments on commit 46177b1

Please sign in to comment.