Merge pull request #21709 from hrydgard/renderdoc-windows-fix

Windows: Restore the GDI-based text drawer, use it if RenderDoc is attached
This commit is contained in:
Henrik Rydgård
2026-05-20 11:32:24 +02:00
committed by GitHub
4 changed files with 319 additions and 6 deletions
+34 -1
View File
@@ -1,5 +1,11 @@
#include "ppsspp_config.h"
#if defined(__linux__) && !defined(__ANDROID__)
# include <dlfcn.h>
#elif defined(_WIN32)
# include <windows.h>
#endif
#include <algorithm>
#include "Common/System/Display.h"
#include "Common/GPU/thin3d.h"
@@ -13,8 +19,27 @@
#include "Common/Render/Text/draw_text_qt.h"
#include "Common/Render/Text/draw_text_android.h"
#include "Common/Render/Text/draw_text_sdl.h"
#include "Common/Render/Text/draw_text_uwp.h"
#include "Common/StringUtils.h"
// TODO: Move this to somewhere more appropriate.
static bool IsRenderDocLoaded() {
#if PPSSPP_PLATFORM(UWP) || PPSSPP_PLATFORM(IOS) || PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(ANDROID)
return false;
#elif defined(_WIN32)
return (GetModuleHandleA("renderdoc.dll") != nullptr);
#elif defined(__linux__)
// Returns a pointer if loaded, otherwise null
void* handle = dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
if (handle) {
dlclose(handle);
return true;
}
return false;
#endif
}
TextDrawer::TextDrawer(Draw::DrawContext *draw) : draw_(draw) {
// These probably shouldn't be state.
dpiScale_ = CalculateDPIScale();
@@ -272,8 +297,16 @@ TextDrawer *TextDrawer::Create(Draw::DrawContext *draw) {
TextDrawer *drawer = nullptr;
#if defined(__LIBRETRO__)
// No text drawer
#elif PPSSPP_PLATFORM(UWP)
drawer = new TextDrawerUWP(draw);
#elif defined(_WIN32) && !defined(USING_QT_UI)
drawer = new TextDrawerWin32(draw);
// If RenderDoc is active, we can't use the UWP drawer since it uses D3D11 to initialize D2D,
// and RenderDoc doesn't permit multiple graphics contexts.
if (IsRenderDocLoaded()) {
drawer = new TextDrawerWin32(draw);
} else {
drawer = new TextDrawerUWP(draw);
}
#elif PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS)
drawer = new TextDrawerCocoa(draw);
#elif defined(USING_QT_UI)
+258 -2
View File
@@ -1,5 +1,261 @@
#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_win.h"
#include "Common/Log.h"
#include "Common/StringUtils.h"
#if defined(_WIN32) && !defined(USING_QT_UI) && !PPSSPP_PLATFORM(UWP)
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
enum {
MAX_TEXT_WIDTH = 4096,
MAX_TEXT_HEIGHT = 512
};
class TextDrawerFontContext {
public:
explicit TextDrawerFontContext(const FontStyle &_style, float _dpiScale) : hFont(0), style(_style), dpiScale(_dpiScale) {
FontStyleFlags styleFlags{};
std::string fontName = GetFontNameForFontStyle(style, &styleFlags);
if (fontName.empty()) {
// Shouldn't happen.
fontName = "Tahoma";
}
int weight = FW_NORMAL;
if (styleFlags & FontStyleFlags::Bold) {
weight = FW_BOLD;
}
if (styleFlags & FontStyleFlags::Light) {
weight = FW_LIGHT;
}
bool italic = (styleFlags & FontStyleFlags::Italic);
int height = style.sizePts;
if (hFont) {
Destroy();
}
// We apparently specify all font sizes in pts (1pt = 1.33px), so divide by only 72 for pixels.
int nHeight = -MulDiv(height, (int)(96.0f * (1.0f / dpiScale)), 72);
hFont = CreateFont(nHeight, 0, 0, 0, weight, italic,
FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, PROOF_QUALITY,
VARIABLE_PITCH, ConvertUTF8ToWString(fontName).c_str());
}
~TextDrawerFontContext() {
Destroy();
}
void Destroy() {
DeleteObject(hFont);
hFont = 0;
}
HFONT hFont = 0;
FontStyle style;
float dpiScale;
};
struct TextDrawerContext {
HDC hDC;
HBITMAP hbmBitmap;
int *pBitmapBits;
};
TextDrawerWin32::TextDrawerWin32(Draw::DrawContext *draw) : TextDrawer(draw), ctx_(nullptr) {
ctx_ = new TextDrawerContext();
ctx_->hDC = CreateCompatibleDC(NULL);
// Load the font files (pass the all flags so we get all filenames);
std::vector<std::string> fonts = GetAllFontFilenames();
for (const auto &iter : fonts) {
std::string fn = "assets/" + iter + ".ttf";
int numFontsAdded = AddFontResourceEx(ConvertUTF8ToWString(fn).c_str(), FR_PRIVATE, NULL);
if (numFontsAdded == 0) {
ERROR_LOG(Log::G3D, "Failed to add font resource from %s", fn.c_str());
}
}
BITMAPINFO bmi{};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = MAX_TEXT_WIDTH;
bmi.bmiHeader.biHeight = -MAX_TEXT_HEIGHT;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biCompression = BI_RGB;
bmi.bmiHeader.biBitCount = 32;
ctx_->hbmBitmap = CreateDIBSection(ctx_->hDC, &bmi, DIB_RGB_COLORS, (VOID**)&ctx_->pBitmapBits, NULL, 0);
_assert_(ctx_->hbmBitmap != nullptr);
SetMapMode(ctx_->hDC, MM_TEXT);
SelectObject(ctx_->hDC, ctx_->hbmBitmap);
}
TextDrawerWin32::~TextDrawerWin32() {
ClearCache();
ClearFonts();
// Unload the fonts.
std::vector<std::string> fonts = GetAllFontFilenames();
for (const auto &iter : fonts) {
std::string fn = "assets/" + iter + ".ttf";
RemoveFontResourceEx(ConvertUTF8ToWString(fn).c_str(), FR_PRIVATE, NULL);
}
DeleteObject(ctx_->hbmBitmap);
DeleteDC(ctx_->hDC);
delete ctx_;
}
void TextDrawerWin32::SetOrCreateFont(const FontStyle &style) {
auto iter = fontMap_.find(style);
if (iter != fontMap_.end()) {
fontStyle_ = style;
return;
}
fontMap_[style] = std::make_unique<TextDrawerFontContext>(style, dpiScale_);
fontStyle_ = style;
}
void TextDrawerWin32::MeasureStringInternal(std::string_view str, float *w, float *h) {
auto iter = fontMap_.find(fontStyle_);
if (iter != fontMap_.end()) {
SelectObject(ctx_->hDC, iter->second->hFont);
} else {
ERROR_LOG(Log::G3D, "Failed to measure string");
return;
}
std::vector<std::string_view> lines;
SplitString(str, '\n', lines);
int extW = 0, extH = 0;
for (auto &line : lines) {
SIZE size;
std::wstring wstr = ConvertUTF8ToWString(line);
if (wstr.empty() && lines.size() > 1) {
// Measure empty lines as if it was a space.
wstr = L" ";
}
GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size);
if (size.cx > extW)
extW = size.cx;
extH += size.cy;
}
*w = extW;
*h = extH;
}
bool TextDrawerWin32::DrawStringBitmap(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"));
auto iter = fontMap_.find(fontStyle_);
if (iter != fontMap_.end()) {
SelectObject(ctx_->hDC, iter->second->hFont);
}
// Set text properties
SetTextColor(ctx_->hDC, 0xFFFFFF);
SetBkColor(ctx_->hDC, 0);
SetTextAlign(ctx_->hDC, TA_TOP);
// This matters for multi-line text - DT_CENTER is horizontal only.
UINT dtAlign = (align & ALIGN_HCENTER) == 0 ? DT_LEFT : DT_CENTER;
RECT textRect = { 0 };
DrawTextExW(ctx_->hDC, (LPWSTR)wstr.c_str(), (int)wstr.size(), &textRect, DT_NOPREFIX | DT_TOP | dtAlign | DT_CALCRECT, 0);
SIZE size;
size.cx = textRect.right;
size.cy = textRect.bottom;
if (size.cx > MAX_TEXT_WIDTH)
size.cx = MAX_TEXT_WIDTH;
if (size.cy > MAX_TEXT_HEIGHT)
size.cy = MAX_TEXT_HEIGHT;
if (size.cx == 0 || size.cy == 0) {
// Don't draw zero-sized textures.
WARN_LOG(Log::G3D, "Text '%.*s' caused a zero size image", (int)str.length(), str.data());
return false;
}
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_;
RECT rc = { 0 };
rc.right = entry.bmWidth;
rc.bottom = entry.bmHeight;
FillRect(ctx_->hDC, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
DrawTextExW(ctx_->hDC, (LPWSTR)wstr.c_str(), (int)wstr.size(), &rc, DT_NOPREFIX | DT_TOP | dtAlign, 0);
// Convert the bitmap to a Thin3D compatible array of 16-bit pixels. Can't use a single channel format
// because we need white. Well, we could using swizzle, but not all our backends support that.
if (texFormat == Draw::DataFormat::R8G8B8A8_UNORM || texFormat == Draw::DataFormat::B8G8R8A8_UNORM) {
bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint32_t));
uint32_t *bitmapData32 = (uint32_t *)&bitmapData[0];
for (int y = 0; y < entry.bmHeight; y++) {
for (int x = 0; x < entry.bmWidth; x++) {
uint8_t bAlpha = (uint8_t)(ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff);
bitmapData32[entry.bmWidth * y + x] = AlphaToPremul8888(bAlpha);
}
}
} else if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) {
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)(ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff);
bitmapData16[entry.bmWidth * y + x] = AlphaToPremul4444(bAlpha);
}
}
} else if (texFormat == Draw::DataFormat::A4R4G4B4_UNORM_PACK16) {
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)(ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff);
bitmapData16[entry.bmWidth * y + x] = AlphaToPremul4444(bAlpha);
}
}
} else if (texFormat == Draw::DataFormat::R8_UNORM) {
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 = ctx_->pBitmapBits[MAX_TEXT_WIDTH * y + x] & 0xff;
bitmapData[entry.bmWidth * y + x] = bAlpha;
}
}
} else {
_assert_msg_(false, "Bad TextDrawer format");
}
return true;
}
void TextDrawerWin32::ClearFonts() {
for (auto &iter : fontMap_) {
iter.second->Destroy();
}
fontMap_.clear();
}
#if defined(_WIN32) && !defined(USING_QT_UI)
// TextDrawerWin32 is an alias of TextDrawerUWP; implementation is in draw_text_uwp.cpp.
#endif
+26 -3
View File
@@ -1,8 +1,31 @@
#pragma once
#include "Common/Render/Text/draw_text_uwp.h"
#include "ppsspp_config.h"
#if defined(_WIN32) && !defined(USING_QT_UI)
using TextDrawerWin32 = TextDrawerUWP;
#include <map>
#include "Common/Render/Text/draw_text.h"
#if defined(_WIN32) && !defined(USING_QT_UI) && !PPSSPP_PLATFORM(UWP)
struct TextDrawerContext;
// Internal struct but all details in .cpp file (pimpl to avoid pulling in excessive headers here)
class TextDrawerFontContext;
class TextDrawerWin32 : public TextDrawer {
public:
TextDrawerWin32(Draw::DrawContext *draw);
~TextDrawerWin32();
void SetOrCreateFont(const FontStyle &style) override;
bool DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override;
protected:
void MeasureStringInternal(std::string_view str, float *w, float *h) override;
bool SupportsColorEmoji() const override { return false; }
void ClearFonts() override;
TextDrawerContext *ctx_;
std::map<FontStyle, std::unique_ptr<TextDrawerFontContext>> fontMap_;
};
#endif
@@ -1650,6 +1650,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
// Workaround for issue #13363 to fix Split/Second game start - it requires text input
// but we don't support it on VR devices.
if (isVRDevice()) {
Log.w(TAG, "VR: Missing UI, supplying default string for text input request");
NativeApp.sendRequestResult(requestID, false, defString, 0);
return true;
}