mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 12:07:31 +01:00
Drawing now takes the style and scale too, like measuring, so the text drawers have no font state left. UIContext keeps its current style and passes it, and fills it in from the theme if nothing has set one yet. PPGe passes its one font. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
451 lines
13 KiB
C++
451 lines
13 KiB
C++
#include "ppsspp_config.h"
|
|
#include "Common/System/Display.h"
|
|
#include "Common/GPU/thin3d.h"
|
|
#include "Common/Data/Hash/Hash.h"
|
|
#include "Common/Data/Text/WrapText.h"
|
|
#include "Common/Data/Encoding/Utf8.h"
|
|
#include "Common/Render/Text/draw_text.h"
|
|
#include "Common/Render/Text/draw_text_uwp.h"
|
|
#include "Common/File/VFS/VFS.h"
|
|
#include "Common/Log.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/File/Path.h"
|
|
|
|
#if defined(_WIN32)
|
|
|
|
#include <string>
|
|
|
|
#include <d3d11.h>
|
|
#include <dxgi1_3.h>
|
|
#include <d2d1_3.h>
|
|
#include <dwrite_3.h>
|
|
|
|
using namespace Microsoft::WRL;
|
|
|
|
enum {
|
|
MAX_TEXT_WIDTH = 4096,
|
|
MAX_TEXT_HEIGHT = 512
|
|
};
|
|
|
|
class TextDrawerFontContext {
|
|
public:
|
|
TextDrawerFontContext(const FontStyle &_style, float _dpiScale, IDWriteFactory4 *factory, IDWriteFontCollection1 *fontCollection) : style(_style), dpiScale(_dpiScale) {
|
|
DWRITE_FONT_STYLE fontStyle = DWRITE_FONT_STYLE_NORMAL;
|
|
DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_NORMAL;
|
|
int height = style.sizePts;
|
|
FontStyleFlags styleFlags = FontStyleFlags::Default;
|
|
std::string fontName = GetFontNameForFontStyle(style, &styleFlags);
|
|
if (styleFlags & FontStyleFlags::Bold) {
|
|
weight = DWRITE_FONT_WEIGHT_BOLD;
|
|
}
|
|
if (styleFlags & FontStyleFlags::Italic) {
|
|
fontStyle = DWRITE_FONT_STYLE_ITALIC;
|
|
}
|
|
|
|
std::wstring wFontName = ConvertUTF8ToWString(fontName);
|
|
|
|
// A font override from the language ini (like "Microsoft YaHei") is a system font, not one of ours.
|
|
// A null collection makes DirectWrite use the system font collection.
|
|
UINT32 familyIndex = 0;
|
|
BOOL exists = FALSE;
|
|
if (FAILED(fontCollection->FindFamilyName(wFontName.c_str(), &familyIndex, &exists)) || !exists) {
|
|
fontCollection = nullptr;
|
|
}
|
|
|
|
HRESULT hr = factory->CreateTextFormat(
|
|
wFontName.c_str(),
|
|
fontCollection,
|
|
weight,
|
|
fontStyle,
|
|
DWRITE_FONT_STRETCH_NORMAL,
|
|
(float)MulDiv(height, (int)(96.0f * (1.0f / dpiScale)), 72),
|
|
L"en-us",
|
|
&textFmt
|
|
);
|
|
if (FAILED(hr)) {
|
|
ERROR_LOG(Log::G3D, "Failed creating text format for font %s", fontName.c_str());
|
|
}
|
|
}
|
|
|
|
~TextDrawerFontContext() {
|
|
if (textFmt) {
|
|
textFmt->Release();
|
|
}
|
|
textFmt = nullptr;
|
|
}
|
|
|
|
IDWriteTextFormat *textFmt = nullptr;
|
|
FontStyle style;
|
|
float dpiScale;
|
|
};
|
|
|
|
struct TextDrawerContext {
|
|
ID2D1Bitmap1 *bitmap = nullptr;
|
|
ID2D1Bitmap1 *mirror_bmp = nullptr;
|
|
};
|
|
|
|
TextDrawerUWP::TextDrawerUWP(Draw::DrawContext *draw) : TextDrawer(draw), ctx_(nullptr) {
|
|
HRESULT hr;
|
|
|
|
#if PPSSPP_PLATFORM(UWP)
|
|
// On UWP we can assume D3D11 and reuse the existing device.
|
|
ID3D11Device *d3ddevice = (ID3D11Device *)draw->GetNativeObject(Draw::NativeObject::DEVICE);
|
|
IDXGIDevice *dxgiDevice;
|
|
hr = d3ddevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
|
|
if (FAILED(hr)) _assert_msg_(false, "ID3DDevice QueryInterface IDXGIDevice failed");
|
|
#else
|
|
// On non-UWP Win32 the draw context may use any backend (Vulkan, GL, etc.), so create
|
|
// an independent WARP (software) D3D11 device solely for D2D/DirectWrite rendering.
|
|
ID3D11Device *d3ddevice = nullptr;
|
|
D3D_FEATURE_LEVEL featureLevel;
|
|
hr = D3D11CreateDevice(
|
|
nullptr,
|
|
D3D_DRIVER_TYPE_WARP,
|
|
nullptr,
|
|
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
|
|
nullptr, 0,
|
|
D3D11_SDK_VERSION,
|
|
&d3ddevice,
|
|
&featureLevel,
|
|
nullptr
|
|
);
|
|
if (FAILED(hr)) _assert_msg_(false, "D3D11CreateDevice (WARP) for text rendering failed");
|
|
IDXGIDevice *dxgiDevice;
|
|
hr = d3ddevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
|
|
d3ddevice->Release();
|
|
if (FAILED(hr)) _assert_msg_(false, "WARP D3D11Device QueryInterface IDXGIDevice failed");
|
|
#endif
|
|
|
|
// Initialize the Direct2D Factory.
|
|
D2D1_FACTORY_OPTIONS options = {};
|
|
D2D1CreateFactory(
|
|
D2D1_FACTORY_TYPE_SINGLE_THREADED,
|
|
__uuidof(ID2D1Factory5),
|
|
&options,
|
|
(void**)&m_d2dFactory
|
|
);
|
|
|
|
// Initialize the DirectWrite Factory.
|
|
DWriteCreateFactory(
|
|
DWRITE_FACTORY_TYPE_SHARED,
|
|
__uuidof(IDWriteFactory5),
|
|
(IUnknown**)&m_dwriteFactory
|
|
);
|
|
|
|
// Create D2D Device and DeviceContext.
|
|
hr = m_d2dFactory->CreateDevice(dxgiDevice, &m_d2dDevice);
|
|
dxgiDevice->Release();
|
|
if (FAILED(hr)) _assert_msg_(false, "D2D CreateDevice failed");
|
|
hr = m_d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, &m_d2dContext);
|
|
if (FAILED(hr)) _assert_msg_(false, "D2D CreateDeviceContext failed");
|
|
|
|
hr = m_dwriteFactory->CreateFontSetBuilder(&m_fontSetBuilder);
|
|
if (FAILED(hr)) ERROR_LOG(Log::System, "CreateFontSetBuilder failed");
|
|
|
|
hr = m_dwriteFactory->CreateInMemoryFontFileLoader(&m_inMemoryLoader);
|
|
if (FAILED(hr)) {
|
|
_assert_msg_(false, "D2D CreateInMemoryFontFileLoader failed");
|
|
}
|
|
hr = m_dwriteFactory->RegisterFontFileLoader(m_inMemoryLoader);
|
|
if (FAILED(hr)) {
|
|
_assert_msg_(false, "D2D RegisterFontFileLoader failed");
|
|
}
|
|
|
|
// Load our fonts.
|
|
const std::vector<std::string> fontFilenames = GetAllFontFilenames();
|
|
for (const auto &fname : fontFilenames) {
|
|
size_t size;
|
|
uint8_t *data = g_VFS.ReadFile((fname + ".ttf").c_str(), &size);
|
|
if (!data) {
|
|
ERROR_LOG(Log::G3D, "Failed to load font file: %s.ttf", fname.c_str());
|
|
continue;
|
|
}
|
|
|
|
ComPtr<IDWriteFontFile> fontFile;
|
|
hr = m_inMemoryLoader->CreateInMemoryFontFileReference(
|
|
m_dwriteFactory,
|
|
data,
|
|
static_cast<UINT32>(size),
|
|
nullptr, // optional last write time
|
|
&fontFile
|
|
);
|
|
|
|
delete[] data;
|
|
|
|
if (FAILED(hr)) {
|
|
ERROR_LOG(Log::G3D, "CreateInMemoryFontFileReference failed for: %s.ttf", fname.c_str());
|
|
continue;
|
|
}
|
|
|
|
m_fontSetBuilder->AddFontFile(fontFile.Get());
|
|
m_fontFiles.push_back(fontFile);
|
|
}
|
|
|
|
hr = m_fontSetBuilder->CreateFontSet(&m_fontSet);
|
|
hr = m_dwriteFactory->CreateFontCollectionFromFontSet(m_fontSet, &m_fontCollection);
|
|
|
|
UINT32 familyCount = m_fontCollection->GetFontFamilyCount();
|
|
for (UINT32 i = 0; i < familyCount; ++i) {
|
|
IDWriteFontFamily1 *family;
|
|
m_fontCollection->GetFontFamily(i, &family);
|
|
|
|
IDWriteLocalizedStrings *names;
|
|
family->GetFamilyNames(&names);
|
|
|
|
UINT32 length = 0;
|
|
names->GetStringLength(0, &length);
|
|
|
|
std::wstring name(length + 1, L'\0');
|
|
names->GetString(0, &name[0], length + 1);
|
|
|
|
std::string nname = ConvertWStringToUTF8(name);
|
|
|
|
// INFO_LOG(Log::G3D, "Font family: %s", nname.c_str());
|
|
|
|
names->Release();
|
|
family->Release();
|
|
}
|
|
|
|
ctx_ = new TextDrawerContext();
|
|
|
|
D2D1_BITMAP_PROPERTIES1 properties{};
|
|
properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
|
properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
|
|
properties.dpiX = 96.0f;
|
|
properties.dpiY = 96.0f;
|
|
properties.bitmapOptions = D2D1_BITMAP_OPTIONS_CANNOT_DRAW | D2D1_BITMAP_OPTIONS_TARGET;
|
|
properties.colorContext = nullptr;
|
|
|
|
// Create main drawing bitmap
|
|
m_d2dContext->CreateBitmap(
|
|
D2D1::SizeU(MAX_TEXT_WIDTH, MAX_TEXT_HEIGHT),
|
|
nullptr,
|
|
0,
|
|
&properties,
|
|
&ctx_->bitmap
|
|
);
|
|
m_d2dContext->SetTarget(ctx_->bitmap);
|
|
|
|
// Create mirror bitmap for CPU readback
|
|
properties.bitmapOptions = D2D1_BITMAP_OPTIONS_CANNOT_DRAW | D2D1_BITMAP_OPTIONS_CPU_READ;
|
|
m_d2dContext->CreateBitmap(
|
|
D2D1::SizeU(MAX_TEXT_WIDTH, MAX_TEXT_HEIGHT),
|
|
nullptr,
|
|
0,
|
|
&properties,
|
|
&ctx_->mirror_bmp
|
|
);
|
|
|
|
m_d2dContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White, 1.0f), &m_d2dWhiteBrush);
|
|
}
|
|
|
|
TextDrawerUWP::~TextDrawerUWP() {
|
|
ClearCache();
|
|
ClearFonts();
|
|
|
|
ctx_->bitmap->Release();
|
|
ctx_->mirror_bmp->Release();
|
|
|
|
m_dwriteFactory->UnregisterFontFileLoader(m_inMemoryLoader);
|
|
m_fontCollection->Release();
|
|
m_fontSet->Release();
|
|
m_fontFiles.clear();
|
|
m_fontSetBuilder->Release();
|
|
m_dwriteFactory->Release();
|
|
m_inMemoryLoader->Release();
|
|
|
|
m_d2dWhiteBrush->Release();
|
|
m_d2dContext->Release();
|
|
m_d2dDevice->Release();
|
|
m_d2dFactory->Release();
|
|
delete ctx_;
|
|
}
|
|
|
|
void TextDrawerUWP::EnsureFont(const FontStyle &style) {
|
|
_dbg_assert_(style.sizePts > 0);
|
|
if (fontMap_.find(style) != fontMap_.end()) {
|
|
return;
|
|
}
|
|
fontMap_[style] = std::make_unique<TextDrawerFontContext>(style, dpiScale_, m_dwriteFactory, m_fontCollection);
|
|
}
|
|
|
|
void TextDrawerUWP::MeasureStringInternal(const FontStyle &style, std::string_view str, float *w, float *h) {
|
|
IDWriteTextFormat *format = nullptr;
|
|
auto iter = fontMap_.find(style);
|
|
if (iter != fontMap_.end()) {
|
|
format = iter->second->textFmt;
|
|
}
|
|
if (!format) return;
|
|
|
|
std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(str, "\n", "\r\n"));
|
|
|
|
format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
|
|
|
|
IDWriteTextLayout *layout = nullptr;
|
|
HRESULT hr = m_dwriteFactory->CreateTextLayout(
|
|
(LPWSTR)wstr.c_str(),
|
|
(int)wstr.size(),
|
|
format,
|
|
MAX_TEXT_WIDTH,
|
|
MAX_TEXT_HEIGHT,
|
|
&layout
|
|
);
|
|
if (FAILED(hr) || !layout) return;
|
|
|
|
DWRITE_TEXT_METRICS metrics;
|
|
layout->GetMetrics(&metrics);
|
|
layout->Release();
|
|
|
|
*w = (int)(metrics.width + 1.0f);
|
|
*h = (int)(metrics.height + 1.0f);
|
|
}
|
|
|
|
bool TextDrawerUWP::DrawStringBitmap(const FontStyle &style, std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) {
|
|
if (str.empty()) {
|
|
bitmapData.clear();
|
|
return false;
|
|
}
|
|
|
|
std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(str, "\n", "\r\n"));
|
|
SIZE size;
|
|
|
|
IDWriteTextFormat *format = nullptr;
|
|
auto iter = fontMap_.find(style);
|
|
if (iter != fontMap_.end()) {
|
|
format = iter->second->textFmt;
|
|
}
|
|
if (!format) {
|
|
bitmapData.clear();
|
|
return false;
|
|
}
|
|
|
|
if (align & ALIGN_HCENTER)
|
|
format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);
|
|
else if (align & ALIGN_LEFT)
|
|
format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
|
|
else if (align & ALIGN_RIGHT)
|
|
format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_TRAILING);
|
|
|
|
IDWriteTextLayout *layout = nullptr;
|
|
HRESULT hr = m_dwriteFactory->CreateTextLayout(
|
|
(LPWSTR)wstr.c_str(),
|
|
(int)wstr.size(),
|
|
format,
|
|
MAX_TEXT_WIDTH,
|
|
MAX_TEXT_HEIGHT,
|
|
&layout
|
|
);
|
|
if (FAILED(hr) || !layout) {
|
|
bitmapData.clear();
|
|
return false;
|
|
}
|
|
|
|
DWRITE_TEXT_METRICS metrics;
|
|
layout->GetMetrics(&metrics);
|
|
|
|
// Set the max size the same as current size, avoiding alignment issues
|
|
layout->SetMaxHeight(metrics.height);
|
|
layout->SetMaxWidth(metrics.width);
|
|
|
|
size.cx = (int)metrics.width + 1;
|
|
size.cy = (int)metrics.height + 1;
|
|
|
|
if (size.cx > MAX_TEXT_WIDTH)
|
|
size.cx = MAX_TEXT_WIDTH;
|
|
if (size.cy > MAX_TEXT_HEIGHT)
|
|
size.cy = MAX_TEXT_HEIGHT;
|
|
|
|
// Prevent zero-sized textures, which can occur. Not worth to avoid
|
|
// creating the texture altogether in this case. One example is a string
|
|
// containing only '\r\n', see issue #10764.
|
|
if (size.cx == 0)
|
|
size.cx = 1;
|
|
if (size.cy == 0)
|
|
size.cy = 1;
|
|
|
|
entry.texture = nullptr;
|
|
entry.width = size.cx;
|
|
entry.height = size.cy;
|
|
entry.bmWidth = (size.cx + 3) & ~3;
|
|
entry.bmHeight = (size.cy + 3) & ~3;
|
|
entry.lastUsedFrame = frameCount_;
|
|
|
|
m_d2dContext->BeginDraw();
|
|
m_d2dContext->Clear();
|
|
m_d2dContext->DrawTextLayout(D2D1::Point2F(0.0f, 0.0f), layout, m_d2dWhiteBrush, fullColor ? D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT : D2D1_DRAW_TEXT_OPTIONS_NONE);
|
|
m_d2dContext->EndDraw();
|
|
|
|
layout->Release();
|
|
|
|
D2D1_POINT_2U dstP = D2D1::Point2U(0, 0);
|
|
D2D1_RECT_U srcR = D2D1::RectU(0, 0, entry.bmWidth, entry.bmHeight);
|
|
D2D1_MAPPED_RECT map;
|
|
ctx_->mirror_bmp->CopyFromBitmap(&dstP, ctx_->bitmap, &srcR);
|
|
hr = ctx_->mirror_bmp->Map(D2D1_MAP_OPTIONS_READ, &map);
|
|
if (FAILED(hr)) {
|
|
bitmapData.clear();
|
|
return false;
|
|
}
|
|
|
|
// Convert the bitmap to a Thin3D compatible array of pixels.
|
|
if (texFormat == Draw::DataFormat::R8G8B8A8_UNORM || texFormat == Draw::DataFormat::B8G8R8A8_UNORM) {
|
|
bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint32_t));
|
|
bool swap = texFormat == Draw::DataFormat::R8G8B8A8_UNORM;
|
|
uint32_t *bitmapData32 = (uint32_t *)&bitmapData[0];
|
|
for (int y = 0; y < entry.bmHeight; y++) {
|
|
uint32_t *bmpLine = (uint32_t *)&map.bits[map.pitch * y];
|
|
for (int x = 0; x < entry.bmWidth; x++) {
|
|
uint32_t v = bmpLine[x];
|
|
if (fullColor) {
|
|
if (swap)
|
|
v = (v & 0xFF00FF00) | ((v >> 16) & 0xFF) | ((v << 16) & 0xFF0000);
|
|
bitmapData32[entry.bmWidth * y + x] = RGBAToPremul8888(v);
|
|
} else {
|
|
bitmapData32[entry.bmWidth * y + x] = AlphaToPremul8888(v >> 24);
|
|
}
|
|
}
|
|
}
|
|
} else if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) {
|
|
_dbg_assert_(!fullColor);
|
|
bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t));
|
|
uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0];
|
|
for (int y = 0; y < entry.bmHeight; y++) {
|
|
for (int x = 0; x < entry.bmWidth; x++) {
|
|
uint8_t bAlpha = (uint8_t)(map.bits[map.pitch * y + x * 4] & 0xff);
|
|
bitmapData16[entry.bmWidth * y + x] = AlphaToPremul4444(bAlpha);
|
|
}
|
|
}
|
|
} else if (texFormat == Draw::DataFormat::A4R4G4B4_UNORM_PACK16) {
|
|
_dbg_assert_(!fullColor);
|
|
bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t));
|
|
uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0];
|
|
for (int y = 0; y < entry.bmHeight; y++) {
|
|
for (int x = 0; x < entry.bmWidth; x++) {
|
|
uint8_t bAlpha = (uint8_t)(map.bits[map.pitch * y + x * 4] & 0xff);
|
|
bitmapData16[entry.bmWidth * y + x] = AlphaToPremul4444(bAlpha);
|
|
}
|
|
}
|
|
} else if (texFormat == Draw::DataFormat::R8_UNORM) {
|
|
_dbg_assert_(!fullColor);
|
|
bitmapData.resize(entry.bmWidth * entry.bmHeight);
|
|
for (int y = 0; y < entry.bmHeight; y++) {
|
|
for (int x = 0; x < entry.bmWidth; x++) {
|
|
uint8_t bAlpha = (uint8_t)(map.bits[map.pitch * y + x * 4] & 0xff);
|
|
bitmapData[entry.bmWidth * y + x] = bAlpha;
|
|
}
|
|
}
|
|
} else {
|
|
_assert_msg_(false, "Bad TextDrawer format");
|
|
}
|
|
|
|
ctx_->mirror_bmp->Unmap();
|
|
return true;
|
|
}
|
|
|
|
void TextDrawerUWP::ClearFonts() {
|
|
fontMap_.clear();
|
|
}
|
|
|
|
#endif
|
|
|