Last active
November 7, 2024 12:25
-
-
Save JustasMasiulis/bee43f5a1280c2359889898f6d093b80 to your computer and use it in GitHub Desktop.
Simple d2d1 overlay
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "overlay_drawer.hpp" | |
#include <iostream> | |
#include <random> | |
#include <chrono> | |
std::minstd_rand gen(std::random_device{}()); | |
const std::uniform_real_distribution<float> uni_col(0.2f, 1.f); | |
const std::uniform_int_distribution<int> uni_pos(100, 1000); | |
const std::uniform_int_distribution<int> uni_sz(30, 100); | |
const auto pos = [&]() { return static_cast<float>(uni_pos(gen)); }; | |
const auto sz = [&]() { return static_cast<float>(uni_sz(gen)); }; | |
D2D1::ColorF gen_color() | |
{ | |
return { uni_col(gen), uni_col(gen), uni_col(gen), uni_col(gen) }; | |
} | |
int main() try | |
{ | |
overlay::drawer d{ overlay::create_basic_window("test") }; | |
// font_resource | |
auto font{ d.create_font(L"Arial", 25) }; | |
// layer_resource | |
auto layer = d.create_layer(); | |
// text_resource | |
auto watermark = d.create_text(font, L"simple d2d1 overlay"); | |
watermark.align(overlay::text_align::right); | |
std::uint64_t frametime = 1; | |
std::uint64_t frametime_sum = 1; | |
std::uint64_t frame_count = 1; | |
std::uint64_t avg_fps = 1; | |
while (true) { | |
using std::chrono::steady_clock; | |
using std::chrono::microseconds; | |
using std::chrono::duration_cast; | |
const auto begin = steady_clock::now(); | |
// message pump ------------------------------------------------------- | |
d.pump_messages(); | |
// average fps -------------------------------------------------------- | |
font.align(overlay::text_align::right); | |
font.draw(L"current fps: " + std::to_wstring(1000000 / frametime) | |
+ L"\nstabilized: " + std::to_wstring(1000000 / (frametime_sum / frame_count)) | |
+ L"\naverage over 1000 frames: " + std::to_wstring(avg_fps) | |
, { 0, 0 } | |
, { 1.f, 0.2f, 1.f, 0.9f }); | |
watermark.draw({ 500,0 }, { 0.6f, 0.2f, 1.f, 0.9f }); | |
{ | |
// needs to get destroyed before call to display | |
auto clip = layer.make_guard({ 0, 100, 10000, 10000 }); | |
// circles ------------------------------------------------------------ | |
for (int i = 0; i < 100; ++i) | |
d.fill_circle({ pos(), pos() }, sz(), gen_color()); | |
// rectangles --------------------------------------------------------- | |
for (int i = 0; i < 100; ++i) { | |
const D2D1_POINT_2F start{ pos() + 700, pos() }; | |
d.fill_rect(start, { start.x + sz(), start.y + sz() }, gen_color()); | |
} | |
// lines -------------------------------------------------------------- | |
for (int i = 0; i < 100; ++i) | |
d.line({ pos(), pos() }, { pos(), pos() }, gen_color()); | |
// text --------------------------------------------------------------- | |
font.align(overlay::text_align::center); | |
POINT p; | |
GetCursorPos(&p); | |
for (int i = 0; i < 100; ++i) | |
font.draw(L"BLAZIN FAST", { p.x + pos(), p.y + pos() }, gen_color()); | |
} | |
d.display(); | |
frametime = static_cast<std::uint64_t>( | |
duration_cast<microseconds>(steady_clock::now() - begin).count()); | |
frametime_sum += frametime; | |
if (++frame_count == 1000) { | |
avg_fps = 1000000 / (frametime_sum / 1000); | |
frametime_sum = 1; | |
frame_count = 1; | |
} | |
} | |
} | |
catch (std::exception& e) | |
{ | |
std::cerr << e.what(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2017 Justas Masiulis | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
#include "overlay_drawer.hpp" | |
#include <system_error> | |
#include <d2d1_3helper.h> | |
#include <d3d11_2.h> | |
using namespace overlay; | |
[[noreturn]] | |
inline void throw_error(const int code, const char* message) | |
{ | |
throw std::system_error(std::error_code(code, std::system_category()), message); | |
} | |
[[noreturn]] | |
inline void throw_last_error(const char* msg) | |
{ | |
throw_error(static_cast<int>(GetLastError()), msg); | |
} | |
inline void HR(const HRESULT result, const char* msg) | |
{ | |
if (result != S_OK) | |
throw_error(result, msg); | |
} | |
ComPtr<IDXGISwapChain1> create_swap_chain(const HWND window | |
, ComPtr<IDXGIDevice>& dxgi_device) | |
{ | |
ComPtr<ID3D11Device> d3_device; | |
HR(D3D11CreateDevice(nullptr | |
, D3D_DRIVER_TYPE_HARDWARE | |
, nullptr | |
, D3D11_CREATE_DEVICE_BGRA_SUPPORT | |
, nullptr | |
, 0 | |
, D3D11_SDK_VERSION | |
, &d3_device | |
, nullptr | |
, nullptr), "D3D11CreateDevice"); | |
HR(d3_device.As(&dxgi_device), "ComPtr<ID3D11Device>::As"); | |
ComPtr<IDXGIFactory2> dx_factory; | |
HR(CreateDXGIFactory2(0 // do not load the debug dll | |
, __uuidof(IDXGIFactory2) | |
, reinterpret_cast<void**>(dx_factory.GetAddressOf())) | |
, "CreateDXGIFactory2"); | |
DXGI_SWAP_CHAIN_DESC1 description = { 0 }; | |
description.Format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; | |
description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; | |
description.BufferCount = 2; | |
description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; | |
description.SampleDesc.Count = 1; | |
RECT rect; | |
if (!GetClientRect(window, &rect)) | |
throw_last_error("GetClientRect"); | |
description.Width = static_cast<UINT>(rect.right - rect.left); | |
description.Height = static_cast<UINT>(rect.bottom - rect.top); | |
ComPtr<IDXGISwapChain1> schain; | |
HR(dx_factory->CreateSwapChainForComposition(dxgi_device.Get() | |
, &description | |
, nullptr | |
, schain.GetAddressOf()), "CreateSwapChainForComposition"); | |
return schain; | |
} | |
ComPtr<ID2D1DeviceContext> create_device_ctx(ComPtr<IDXGIDevice>& dxgi_device) | |
{ | |
// if you manually close the window a breakpoint will be triggered | |
// and say that you have leaks because the destructors are not run | |
const D2D1_FACTORY_OPTIONS options = { D2D1_DEBUG_LEVEL_NONE }; | |
ComPtr<ID2D1Factory2> d2_factory; | |
HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED | |
, options | |
, d2_factory.GetAddressOf()) | |
, "D2D1CreateFactory"); | |
// create the d2d device that links back to the d3d device | |
ComPtr<ID2D1Device1> d2_device; | |
HR(d2_factory->CreateDevice(dxgi_device.Get(), d2_device.GetAddressOf()), | |
"ID2D1Factory2::CreateDevice"); | |
// Create the d2d device context that is the actual render target | |
// and exposes drawing commands | |
ComPtr<ID2D1DeviceContext> dev_context; | |
HR(d2_device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS | |
, dev_context.GetAddressOf()), "ID2D1Device1::CreateDeviceContext"); | |
return dev_context; | |
} | |
ComPtr<IDCompositionTarget> create_composition_target(const HWND window | |
, ComPtr<IDXGIDevice>& dxgi_device | |
, ComPtr<ID2D1DeviceContext>& dev_context | |
, ComPtr<IDXGISwapChain1>& swap_chain) | |
{ | |
// retrieve back buffer | |
ComPtr<IDXGISurface2> surface; | |
HR(swap_chain->GetBuffer(0 // index | |
, __uuidof(IDXGISurface2) | |
, reinterpret_cast<void**>(surface.GetAddressOf())) | |
, "IDXGISwapChain1::GetBuffer"); | |
// create a d2d bitmap that points to the swap chain surface | |
D2D1_BITMAP_PROPERTIES1 properties = {}; | |
properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; | |
properties.pixelFormat.format = DXGI_FORMAT_R8G8B8A8_UNORM; | |
properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | |
| D2D1_BITMAP_OPTIONS_CANNOT_DRAW; | |
ComPtr<ID2D1Bitmap1> bitmap; | |
HR(dev_context->CreateBitmapFromDxgiSurface(surface.Get() | |
, properties | |
, bitmap.GetAddressOf()), "ID2D1DeviceContext::CreateBitmapFromDxgiSurface"); | |
dev_context->SetTarget(bitmap.Get()); | |
ComPtr<IDCompositionDevice> comp_device; | |
HR(DCompositionCreateDevice(dxgi_device.Get() | |
, __uuidof(IDCompositionDevice) | |
, reinterpret_cast<void**>(comp_device.GetAddressOf())) | |
, "DCompositionCreateDevice"); | |
ComPtr<IDCompositionTarget> comp_target; | |
HR(comp_device->CreateTargetForHwnd(window | |
, true // topmost | |
, comp_target.GetAddressOf()), "IDCompositionTarget::CreateTargetForHwnd"); | |
ComPtr<IDCompositionVisual> visual; | |
HR(comp_device->CreateVisual(visual.GetAddressOf()) | |
, "IDCompositionDevice::CreateVisual"); | |
HR(visual->SetContent(swap_chain.Get()) | |
, "IDCompositionVisual::SetContent"); | |
HR(comp_target->SetRoot(visual.Get()) | |
, "IDCompositionTarget::SetRoot"); | |
HR(comp_device->Commit() | |
, "IDCompositionDevice::Commit"); | |
return comp_target; | |
} | |
layer_resource::layer_resource(ID2D1DeviceContext* dc, ComPtr<ID2D1Layer>&& layer) | |
: _dc(dc) | |
, _layer(std::move(layer)) {} | |
void layer_resource::push(const D2D1_RECT_F& rect, float opacity) | |
{ | |
// Push the layer with the content bounds. | |
_dc->PushLayer(D2D1::LayerParameters(rect | |
, nullptr | |
, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE | |
, D2D1::IdentityMatrix() | |
, opacity | |
, nullptr | |
, D2D1_LAYER_OPTIONS_NONE) | |
, _layer.Get()); | |
} | |
void layer_resource::pop() | |
{ | |
_dc->PopLayer(); | |
} | |
layer_resource::guard_t layer_resource::make_guard(const D2D1_RECT_F& rect, float opacity) | |
{ | |
push(rect, opacity); | |
return guard_t{ _dc }; | |
} | |
ID2D1SolidColorBrush* drawer::local_brush(const D2D1::ColorF & color) | |
{ | |
static thread_local auto brush = [](ComPtr<ID2D1DeviceContext>& dev_context) { | |
ComPtr<ID2D1SolidColorBrush> local_brush; | |
HR(dev_context->CreateSolidColorBrush({ 0,0,0,0 } | |
, local_brush.GetAddressOf()), "CreateSolidColorBrush"); | |
return local_brush; | |
}(_dev_context); | |
brush->SetColor(color); | |
return brush.Get(); | |
} | |
drawer::drawer(HWND window) | |
: _window_handle(window) | |
{ | |
ComPtr<IDXGIDevice> dxgi_device; | |
_swap_chain = create_swap_chain(window, dxgi_device); | |
_dev_context = create_device_ctx(dxgi_device); | |
_comp_target = create_composition_target(window, dxgi_device, _dev_context, _swap_chain); | |
// create dwrite factory to generate text layouts | |
HR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED | |
, __uuidof(IDWriteFactory) | |
, reinterpret_cast<IUnknown**>(_dwrite_factory.GetAddressOf())) | |
, "DWriteCreateFactory"); | |
// we need to begin drawing immediately because we exposed essentially | |
// only the end method | |
_dev_context->BeginDraw(); | |
_dev_context->Clear(); | |
} | |
drawer::~drawer() noexcept | |
{ | |
_dev_context->EndDraw(); | |
} | |
void drawer::display() | |
{ | |
HR(_dev_context->EndDraw(), "ID2D1DeviceContext::EndDraw"); | |
// should probably put error handling here but this can fail for stupid reasons | |
_swap_chain->Present(0, 0); | |
_dev_context->BeginDraw(); | |
_dev_context->Clear(); | |
} | |
void drawer::pump_messages() const | |
{ | |
MSG m; | |
if (PeekMessageA(&m, _window_handle, 0, 0, PM_REMOVE)) { | |
TranslateMessage(&m); | |
DispatchMessageA(&m); | |
} | |
} | |
void drawer::circle(const D2D1_POINT_2F& center | |
, float radius | |
, const D2D1::ColorF& color | |
, float width) | |
{ | |
ellipse(center, radius, radius, color, width); | |
} | |
void drawer::fill_circle(const D2D1_POINT_2F& center | |
, float radius | |
, const D2D1::ColorF& color) | |
{ | |
fill_ellipse(center, radius, radius, color); | |
} | |
void drawer::ellipse(const D2D1_POINT_2F& center | |
, float radius_x | |
, float radius_y | |
, const D2D1::ColorF& color | |
, float width) | |
{ | |
_dev_context->DrawEllipse({ center, radius_x, radius_y }, local_brush(color), width); | |
} | |
void drawer::fill_ellipse(const D2D1_POINT_2F& center | |
, float radius_x | |
, float radius_y | |
, const D2D1::ColorF& color) | |
{ | |
_dev_context->FillEllipse({ center, radius_x, radius_y }, local_brush(color)); | |
} | |
void drawer::rect(const D2D1_RECT_F& rect, const D2D1::ColorF& color, float width) | |
{ | |
_dev_context->DrawRectangle(rect, local_brush(color), width); | |
} | |
void drawer::fill_rect(const D2D1_RECT_F& rect, const D2D1::ColorF& color) | |
{ | |
_dev_context->FillRectangle(rect, local_brush(color)); | |
} | |
void overlay::drawer::rounded_rect(const D2D1_RECT_F & rect, const D2D1::ColorF & color, float rounding, float width) | |
{ | |
_dev_context->DrawRoundedRectangle({ rect, rounding, rounding, }, local_brush(color), width); | |
} | |
void overlay::drawer::fill_rounded_rect(const D2D1_RECT_F & rect, const D2D1::ColorF & color, float rounding) | |
{ | |
_dev_context->FillRoundedRectangle({ rect, rounding, rounding, }, local_brush(color)); | |
} | |
void drawer::line(const D2D1_POINT_2F& start | |
, const D2D1_POINT_2F& end | |
, const D2D1::ColorF& color | |
, float width) | |
{ | |
_dev_context->DrawLine(start, end, local_brush(color), width); | |
} | |
font_resource drawer::create_font(const std::wstring& font_family | |
, float size | |
, DWRITE_FONT_WEIGHT weight | |
, DWRITE_FONT_STYLE style) | |
{ | |
ComPtr<IDWriteTextFormat> format; | |
HR(_dwrite_factory->CreateTextFormat(font_family.c_str() | |
, nullptr | |
, weight | |
, style | |
, DWRITE_FONT_STRETCH_NORMAL | |
, size | |
, L"" | |
, format.GetAddressOf()) | |
, "IDWriteTextFormat::CreateTextFormat"); | |
return { std::move(format), _dev_context.Get() }; | |
} | |
text_resource overlay::drawer::create_text(const font_resource & wrapper | |
, const std::wstring & text) | |
{ | |
ComPtr<IDWriteTextLayout> layout; | |
_dwrite_factory->CreateTextLayout(text.c_str() | |
, static_cast<UINT>(text.size()) | |
, wrapper.format() | |
, 0 | |
, 0 | |
, layout.GetAddressOf()); | |
return { std::move(layout), _dev_context.Get() }; | |
} | |
layer_resource drawer::create_layer() | |
{ | |
ComPtr<ID2D1Layer> layer; | |
HR(_dev_context->CreateLayer(layer.GetAddressOf()) | |
, "ID2D1DeviceContext::CreateLayer"); | |
return { _dev_context.Get(), std::move(layer) }; | |
} | |
drawer::translation_guard drawer::make_translation(const D2D1_POINT_2F & point, bool accumulate) | |
{ | |
if (accumulate) { | |
auto translation = get_transform(); | |
translation._31 += point.x; | |
translation._32 += point.y; | |
_dev_context->SetTransform(translation); | |
return translation_guard(_dev_context.Get(), | |
{ translation._31 - point.x, translation._32 - point.y }); | |
} | |
else { | |
_dev_context->SetTransform(D2D1::Matrix3x2F::Translation(point.x, point.y)); | |
return translation_guard(_dev_context.Get()); | |
} | |
} | |
D2D1_POINT_2F drawer::get_translation() const | |
{ | |
const auto mat = get_transform(); | |
return { mat._31, mat._32 }; | |
} | |
D2D1_MATRIX_3X2_F drawer::get_transform() const | |
{ | |
D2D1_MATRIX_3X2_F mat; | |
_dev_context->GetTransform(&mat); | |
return mat; | |
} | |
font_resource::font_resource(ComPtr<IDWriteTextFormat>&& format | |
, ID2D1DeviceContext* context) | |
: _dc(context) | |
, _format(std::move(format)) | |
{ | |
// since we set the size of our text rect to 0 we need to disable wrapping | |
HR(_format->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP) | |
, "IDWriteTextFormat::SetWordWrapping"); | |
HR(_dc->CreateSolidColorBrush({ 0,0,0,0 }, _brush.GetAddressOf()) | |
, "CreateSolidColorBrush"); | |
} | |
void font_resource::align(text_align horizontal_alignment) | |
{ | |
HR(_format->SetTextAlignment(static_cast<DWRITE_TEXT_ALIGNMENT>(horizontal_alignment)) | |
, "IDWriteTextFormat::SetTextAlignment"); | |
} | |
void overlay::font_resource::align_vert(vert_text_align vertical_alignment) | |
{ | |
HR(_format->SetParagraphAlignment(static_cast<DWRITE_PARAGRAPH_ALIGNMENT>(vertical_alignment)) | |
, "IDWriteTextFormat::SetParagraphAlignment"); | |
} | |
void font_resource::draw(const std::wstring& text | |
, const D2D1_POINT_2F pos | |
, const D2D1::ColorF& color) | |
{ | |
_brush->SetColor(color); | |
_dc->DrawTextA(text.c_str() | |
, static_cast<UINT32>(text.size()) | |
, _format.Get() | |
, { pos.x, pos.y, pos.x, pos.y } | |
, _brush.Get()); | |
} | |
HWND overlay::create_basic_window(const char* window_name | |
, int width | |
, int height | |
, WNDPROC wndproc) | |
{ | |
WNDCLASSA wc = { 0 }; | |
wc.lpszClassName = window_name; | |
wc.style = CS_HREDRAW | CS_VREDRAW; | |
wc.lpfnWndProc = wndproc; | |
const auto class_atom = RegisterClassA(&wc); | |
if (!class_atom) | |
throw_last_error("RegisterClassA"); | |
const auto flags = WS_EX_NOREDIRECTIONBITMAP | |
| WS_EX_LAYERED | |
| WS_EX_TRANSPARENT | |
| WS_EX_TOPMOST; | |
if (width == -1) | |
width = GetSystemMetrics(SM_CXSCREEN); | |
if (height == -1) | |
height = GetSystemMetrics(SM_CYSCREEN); | |
const auto window = CreateWindowExA(flags | |
, wc.lpszClassName | |
, window_name | |
, WS_VISIBLE | WS_POPUP | |
, -1 | |
, -1 | |
, width | |
, height | |
, nullptr | |
, nullptr | |
, nullptr | |
, nullptr); | |
if (!window) | |
throw_last_error("CreateWindowExA"); | |
return window; | |
} | |
overlay::text_resource::text_resource(ComPtr<IDWriteTextLayout>&& layout, ID2D1DeviceContext * context) | |
: _dc(context) | |
, _layout(std::move(layout)) | |
{ | |
HR(_dc->CreateSolidColorBrush({ 0,0,0,0 }, _brush.GetAddressOf()) | |
, "CreateSolidColorBrush"); | |
DWRITE_TEXT_METRICS metric; | |
HR(_layout->GetMetrics(&metric), "IDWriteTextLayout::GetMetrics"); | |
_width = metric.width; | |
_height = metric.height; | |
} | |
void text_resource::align(text_align horizontal_alignment) | |
{ | |
HR(_layout->SetTextAlignment(static_cast<DWRITE_TEXT_ALIGNMENT>(horizontal_alignment)) | |
, "IDWriteTextLayout::SetTextAlignment"); | |
} | |
void text_resource::align_vert(vert_text_align vertical_alignment) | |
{ | |
HR(_layout->SetParagraphAlignment(static_cast<DWRITE_PARAGRAPH_ALIGNMENT>(vertical_alignment)) | |
, "IDWriteTextLayout::SetParagraphAlignment"); | |
} | |
void text_resource::draw(const D2D1_POINT_2F pos, const D2D1::ColorF & color) | |
{ | |
_brush->SetColor(color); | |
_dc->DrawTextLayout(pos, _layout.Get(), _brush.Get()); | |
} | |
void drawer::_translation_destructor::operator()(ID2D1DeviceContext * dc) | |
{ | |
dc->SetTransform(D2D1::Matrix3x2F::Translation(previous.x, previous.y)); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2017 Justas Masiulis | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
#ifndef JM_OVERLAY_DRAWER_HPP | |
#define JM_OVERLAY_DRAWER_HPP | |
#define NOMINMAX | |
#include <string> | |
#include <memory> | |
#include <dwrite_3.h> | |
#include <dcomp.h> | |
#include <wrl.h> | |
// pragma comment is not portable | |
#ifdef _MSC_VER | |
#pragma comment(lib, "dxgi") | |
#pragma comment(lib, "d3d11") | |
#pragma comment(lib, "d2d1") | |
#pragma comment(lib, "dcomp") | |
#pragma comment(lib, "dwrite.lib") | |
#pragma comment(lib, "user32.lib") | |
#endif | |
namespace overlay { | |
template<typename T> | |
using ComPtr = Microsoft::WRL::ComPtr<T>; | |
inline LRESULT CALLBACK _default_wndproc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) | |
{ | |
if (message == WM_DESTROY) | |
{ | |
PostQuitMessage(0); | |
return 0; | |
} | |
return DefWindowProc(window, message, wparam, lparam); | |
} | |
// value of -1 for width and height means the window will be made fullscreen | |
HWND create_basic_window(const char* window_name | |
, int width = -1 | |
, int height = -1 | |
, WNDPROC wndproc = _default_wndproc); | |
enum class text_align { | |
// on the left of drawing position | |
left = DWRITE_TEXT_ALIGNMENT_TRAILING, | |
center = DWRITE_TEXT_ALIGNMENT_CENTER, | |
// on the right of drawing position | |
right = DWRITE_TEXT_ALIGNMENT_LEADING | |
}; | |
enum class vert_text_align { | |
// above the drawing position | |
top = DWRITE_PARAGRAPH_ALIGNMENT_FAR, | |
center = DWRITE_PARAGRAPH_ALIGNMENT_CENTER, | |
// below the drawing position | |
bottom = DWRITE_PARAGRAPH_ALIGNMENT_NEAR | |
}; | |
class font_resource { | |
// owned by overlay drawer | |
ID2D1DeviceContext* _dc; | |
ComPtr<IDWriteTextFormat> _format; | |
ComPtr<ID2D1SolidColorBrush> _brush; | |
public: | |
font_resource(ComPtr<IDWriteTextFormat>&& format, ID2D1DeviceContext* context); | |
void align(text_align horizontal_alignment); | |
void align_vert(vert_text_align vertical_alignment); | |
void draw(const std::wstring& text | |
, const D2D1_POINT_2F pos | |
, const D2D1::ColorF& color); | |
IDWriteTextFormat* format() const noexcept { return _format.Get(); } | |
// TextFormat doesn't really allow to change much of it. | |
// only getters about the params with which it was created are possible | |
}; | |
class text_resource { | |
// owned by overlay drawer | |
ID2D1DeviceContext* _dc; | |
ComPtr<IDWriteTextLayout> _layout; | |
ComPtr<ID2D1SolidColorBrush> _brush; | |
float _width; | |
float _height; | |
public: | |
text_resource(ComPtr<IDWriteTextLayout>&& layout, ID2D1DeviceContext* context); | |
void align(text_align horizontal_alignment); | |
void align_vert(vert_text_align vertical_alignment); | |
void draw(const D2D1_POINT_2F pos | |
, const D2D1::ColorF& color); | |
float width() const noexcept { return _width; } | |
float height() const noexcept { return _height; } | |
IDWriteTextLayout* layout() const noexcept { return _layout.Get(); } | |
}; | |
class layer_resource { | |
ID2D1DeviceContext* _dc; | |
ComPtr<ID2D1Layer> _layer; | |
struct _popper { | |
void operator()(ID2D1DeviceContext* dc) const { dc->PopLayer(); } | |
}; | |
public: | |
layer_resource(ID2D1DeviceContext* dc, ComPtr<ID2D1Layer>&& layer); | |
using guard_t = std::unique_ptr <ID2D1DeviceContext, _popper>; | |
void push(const D2D1_RECT_F& rect, float opacity); | |
void pop(); | |
guard_t make_guard(const D2D1_RECT_F& rect, float opacity = 1.f); | |
}; | |
class drawer { | |
// must be preserved for drawing | |
ComPtr<IDXGISwapChain1> _swap_chain; | |
ComPtr<ID2D1DeviceContext> _dev_context; | |
ComPtr<IDCompositionTarget> _comp_target; | |
// needed for font layout creation | |
ComPtr<IDWriteFactory> _dwrite_factory; | |
// can be retrieved only from swapchain that was created with ...ForHwnd function | |
HWND _window_handle; | |
// thread safe buffer storage | |
ID2D1SolidColorBrush* local_brush(const D2D1::ColorF &color); | |
struct _translation_destructor { | |
D2D1_POINT_2F previous{ 0.f,0.f }; | |
void operator()(ID2D1DeviceContext* dc); | |
}; | |
public: | |
using translation_guard = std::unique_ptr<ID2D1DeviceContext, _translation_destructor>; | |
explicit drawer(HWND window); | |
// need to call EndDraw | |
~drawer() noexcept; | |
HWND window_handle() const noexcept { return _window_handle; } | |
// call this after finishing to draw figures | |
void display(); | |
void pump_messages() const; | |
void circle(const D2D1_POINT_2F& center, float radius, const D2D1::ColorF& color, float width = 1.f); | |
void fill_circle(const D2D1_POINT_2F& center, float radius, const D2D1::ColorF& color); | |
void ellipse(const D2D1_POINT_2F& center | |
, float radius_x | |
, float radius_y | |
, const D2D1::ColorF& color | |
, float width = 1.f); | |
void fill_ellipse(const D2D1_POINT_2F& center | |
, float radius_x | |
, float radius_y | |
, const D2D1::ColorF& color); | |
void rect(const D2D1_RECT_F& rect, const D2D1::ColorF& color, float width = 1); | |
void fill_rect(const D2D1_RECT_F& rect, const D2D1::ColorF& color); | |
void rect(const D2D1_POINT_2F& start | |
, const D2D1_POINT_2F& end | |
, const D2D1::ColorF& color | |
, float width = 1.f) | |
{ | |
rect({ start.x, start.y, end.x, end.y }, color, width); | |
} | |
void fill_rect(const D2D1_POINT_2F& start | |
, const D2D1_POINT_2F& end | |
, const D2D1::ColorF& color) | |
{ | |
fill_rect({ start.x, start.y, end.x, end.y }, color); | |
} | |
void rounded_rect(const D2D1_RECT_F& rect, const D2D1::ColorF& color, float rounding = 1, float width = 1); | |
void fill_rounded_rect(const D2D1_RECT_F& rect, const D2D1::ColorF& color, float rounding = 1); | |
void line(const D2D1_POINT_2F& start | |
, const D2D1_POINT_2F& end | |
, const D2D1::ColorF& color | |
, float width = 1.f); | |
// the parameters cannot be changed after creation. | |
// I would recommend not creating multiple copies of same font, but | |
// they seem to be cached by DXGI so it may be fine | |
font_resource create_font(const std::wstring& font_family | |
, float size | |
, DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_REGULAR | |
, DWRITE_FONT_STYLE style = DWRITE_FONT_STYLE_NORMAL); | |
text_resource create_text(const font_resource& wrapper, const std::wstring& text); | |
layer_resource create_layer(); | |
/// if accumulate is set to true previous translation value is added | |
translation_guard make_translation(const D2D1_POINT_2F& point, bool accumulate = true); | |
D2D1_POINT_2F get_translation() const; | |
D2D1_MATRIX_3X2_F get_transform() const; | |
}; | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment