최적화 및 기능 확장 DWrite 클래스
// DWrite.h
#pragma once
#include <d2d1_1.h>
#include <dwrite.h>
#include <dxgi1_2.h>
#include <d3d11.h>
#include <wrl/client.h>
#include <unordered_map>
#include <string>
#include <functional>
#include <stdexcept>
#include <sstream>
#include <iomanip>
using Microsoft::WRL::ComPtr;
// 간단한 RGBA 색상 구조체
typedef struct Color {
float x, y, z, a;
uint32_t toARGB() const {
uint32_t r = static_cast<uint32_t>(x * 255) & 0xFF;
uint32_t g = static_cast<uint32_t>(y * 255) & 0xFF;
uint32_t b = static_cast<uint32_t>(z * 255) & 0xFF;
uint32_t aa = static_cast<uint32_t>(a * 255) & 0xFF;
return (aa << 24) | (r << 16) | (g << 8) | b;
}
} TextColor;
// 지원할 기본 폰트 매핑 (locale -> font 이름)
static const std::unordered_map<std::wstring, std::wstring> DefaultLocaleFonts = {
{L"en-US", L"Segoe UI"},
{L"zh-CN", L"Microsoft YaHei"},
{L"ko-KR", L"맑은 고딕"},
{L"ja-JP", L"Meiryo"}
};
class DWrite {
public:
DWrite(ID3D11Device* d3dDevice, IDXGISwapChain* swapChain);
~DWrite();
// 리사이즈 시에만 호출
void OnResize(UINT width, UINT height);
// 텍스트 렌더링 (locale: en-US, zh-CN, ko-KR, ja-JP 등)
void RenderText(
const std::wstring& text,
const D2D1_RECT_F& rect,
float size = 20.0f,
const std::wstring& font = L"", // 빈 문자열 시 locale 기본 폰트 사용
const std::wstring& locale = L"en-US",
TextColor color = { 1,1,1,1 },
DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE style = DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH stretch = DWRITE_FONT_STRETCH_NORMAL
);
private:
// DirectWrite/2D 팩토리 및 컨텍스트
ComPtr<IDWriteFactory> writeFactory;
ComPtr<ID2D1Factory1> d2dFactory;
ComPtr<ID2D1Device> d2dDevice;
ComPtr<ID2D1DeviceContext> d2dContext;
ComPtr<ID2D1Bitmap1> targetBitmap;
ComPtr<ID3D11Device> d3dDevice;
ComPtr<IDXGISwapChain> swapChain;
// 캐시 키 정의 및 해시
struct BrushKey {
TextColor c;
bool operator==(BrushKey const& o) const noexcept { return c.toARGB() == o.c.toARGB(); }
};
struct BrushKeyHash {
size_t operator()(BrushKey const& k) const noexcept {
return std::hash<uint32_t>()(k.c.toARGB());
}
};
struct TextFormatKey {
std::wstring font;
std::wstring locale;
float size;
DWRITE_FONT_WEIGHT weight;
DWRITE_FONT_STYLE style;
DWRITE_FONT_STRETCH stretch;
bool operator==(TextFormatKey const& o) const noexcept {
return font == o.font && locale == o.locale && size == o.size
&& weight == o.weight && style == o.style && stretch == o.stretch;
}
};
struct TextFormatKeyHash {
size_t operator()(TextFormatKey const& k) const noexcept {
size_t h = std::hash<std::wstring>()(k.font);
h ^= std::hash<std::wstring>()(k.locale) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<float>()(k.size) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<uint32_t>()((uint32_t)k.weight) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<uint32_t>()((uint32_t)k.style) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<uint32_t>()((uint32_t)k.stretch) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
}
};
struct TextLayoutKey {
std::wstring text;
ComPtr<IDWriteTextFormat> format;
float maxW, maxH;
bool operator==(TextLayoutKey const& o) const noexcept {
return text == o.text && format.Get() == o.format.Get()
&& maxW == o.maxW && maxH == o.maxH;
}
};
struct TextLayoutKeyHash {
size_t operator()(TextLayoutKey const& k) const noexcept {
size_t h = std::hash<std::wstring>()(k.text);
h ^= std::hash<void*>()((void*)k.format.Get()) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<float>()(k.maxW) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<float>()(k.maxH) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
}
};
// 캐시 컨테이너
std::unordered_map<BrushKey, ComPtr<ID2D1SolidColorBrush>, BrushKeyHash> brushCache;
std::unordered_map<TextFormatKey, ComPtr<IDWriteTextFormat>, TextFormatKeyHash> formatCache;
std::unordered_map<TextLayoutKey, ComPtr<IDWriteTextLayout>, TextLayoutKeyHash> layoutCache;
// 헬퍼 함수
ComPtr<ID2D1SolidColorBrush> GetBrush(TextColor color);
ComPtr<IDWriteTextFormat> GetTextFormat(
const std::wstring& font,
const std::wstring& locale,
float size,
DWRITE_FONT_WEIGHT,
DWRITE_FONT_STYLE,
DWRITE_FONT_STRETCH
);
ComPtr<IDWriteTextLayout> GetTextLayout(
const std::wstring& text,
ComPtr<IDWriteTextFormat> format,
float maxW, float maxH
);
void ThrowIfFailed(HRESULT hr, const std::wstring& msg);
};
#include "DWrite.h"
#include <d2d1_1helper.h>
DWrite::DWrite(ID3D11Device* d3dDev, IDXGISwapChain* sc)
: d3dDevice(d3dDev), swapChain(sc)
{
HRESULT hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(writeFactory.GetAddressOf())
); ThrowIfFailed(hr, L"DWriteCreateFactory");
D2D1_FACTORY_OPTIONS opts = {};
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, opts, d2dFactory.GetAddressOf());
ThrowIfFailed(hr, L"D2D1CreateFactory");
ComPtr<IDXGIDevice> dxgiDev;
hr = d3dDevice.As(&dxgiDev); ThrowIfFailed(hr, L"QueryInterface IDXGIDevice");
hr = d2dFactory->CreateDevice(dxgiDev.Get(), d2dDevice.GetAddressOf());
ThrowIfFailed(hr, L"CreateDevice");
hr = d2dDevice->CreateDeviceContext(
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
d2dContext.GetAddressOf()
); ThrowIfFailed(hr, L"CreateDeviceContext");
// 초기 설정
DXGI_SWAP_CHAIN_DESC desc;
swapChain->GetDesc(&desc);
OnResize(desc.BufferDesc.Width, desc.BufferDesc.Height);
}
DWrite::~DWrite() {}
void DWrite::OnResize(UINT width, UINT height) {
targetBitmap.Reset();
ComPtr<IDXGISurface> surf;
ThrowIfFailed(swapChain->GetBuffer(0, __uuidof(IDXGISurface), reinterpret_cast<void**>(surf.GetAddressOf())), L"GetBuffer");
D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
D2D1::PixelFormat(DXGI_FORMAT_R8G8B8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
96.f, 96.f
);
ThrowIfFailed(d2dContext->CreateBitmapFromDxgiSurface(surf.Get(), &props, targetBitmap.GetAddressOf()), L"CreateBitmap");
d2dContext->SetTarget(targetBitmap.Get());
}
ComPtr<ID2D1SolidColorBrush> DWrite::GetBrush(TextColor color) {
BrushKey key{ color };
auto it = brushCache.find(key);
if (it != brushCache.end()) return it->second;
ComPtr<ID2D1SolidColorBrush> brush;
ThrowIfFailed(d2dContext->CreateSolidColorBrush(
D2D1::ColorF(color.x, color.y, color.z, color.a),
brush.GetAddressOf()), L"CreateSolidBrush");
brushCache[key] = brush;
return brush;
}
ComPtr<IDWriteTextFormat> DWrite::GetTextFormat(
const std::wstring& font, const std::wstring& locale,
float size, DWRITE_FONT_WEIGHT weight,
DWRITE_FONT_STYLE style, DWRITE_FONT_STRETCH stretch
) {
// 빈 폰트 시 locale 기본 폰트 사용
std::wstring actualFont = font.empty()
? DefaultLocaleFonts.at(locale)
: font;
TextFormatKey key{ actualFont, locale, size, weight, style, stretch };
auto it = formatCache.find(key);
if (it != formatCache.end()) return it->second;
ComPtr<IDWriteTextFormat> fmt;
ThrowIfFailed(writeFactory->CreateTextFormat(
actualFont.c_str(), nullptr,
weight, style, stretch,
size, locale.c_str(),
fmt.GetAddressOf()), L"CreateTextFormat");
formatCache[key] = fmt;
return fmt;
}
ComPtr<IDWriteTextLayout> DWrite::GetTextLayout(
const std::wstring& text,
ComPtr<IDWriteTextFormat> format,
float maxW, float maxH
) {
TextLayoutKey key{ text, format, maxW, maxH };
auto it = layoutCache.find(key);
if (it != layoutCache.end()) return it->second;
ComPtr<IDWriteTextLayout> layout;
ThrowIfFailed(writeFactory->CreateTextLayout(
text.c_str(), (UINT32)text.size(),
format.Get(), maxW, maxH,
layout.GetAddressOf()), L"CreateTextLayout");
layoutCache[key] = layout;
return layout;
}
void DWrite::RenderText(
const std::wstring& text,
const D2D1_RECT_F& rect,
float size,
const std::wstring& font,
const std::wstring& locale,
TextColor color,
DWRITE_FONT_WEIGHT weight,
DWRITE_FONT_STYLE style,
DWRITE_FONT_STRETCH stretch
) {
auto brush = GetBrush(color);
auto fmt = GetTextFormat(font, locale, size, weight, style, stretch);
auto layout = GetTextLayout(text, fmt, rect.right - rect.left, rect.bottom - rect.top);
d2dContext->DrawTextLayout(D2D1::Point2F(rect.left, rect.top), layout.Get(), brush.Get());
}
void DWrite::ThrowIfFailed(HRESULT hr, const std::wstring& msg) {
if (FAILED(hr)) {
std::ostringstream oss;
oss << "0x" << std::hex << static_cast<uint32_t>(hr);
throw std::runtime_error("DWrite error " + oss.str() + ": " + std::string(msg.begin(), msg.end()));
}
}
추후에 기술소개서에
어느 부분이 확장되었는지,
의도가 뭔지
최적화 된 부분은 어디인지
최적화가 왜 필요했는지
그 의도대로 되었는지 적기
확장 전 후 부분 코드 비교하며 설명하기
'DevLog > D2D11 프로젝트' 카테고리의 다른 글
D2D11HollowKnight - StatePatern을 Class화 하는 장점 (0) | 2025.03.17 |
---|---|
Pain Point C++에서 델리게이트같은 함수 만들기 (0) | 2024.10.31 |
상속구조를 이용한 유한 상태 기계 + Component Pattern을 곁들인... (4) | 2024.10.27 |
[Devlog-DX11] HOLLOW KNIGHT 모작 - Video - 블러 처리 시도 (2) | 2024.10.19 |
[Devlog-DX11] HOLLOW KNIGHT 모작 - Audio 기능 (1) | 2024.10.17 |