mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-21 02:09:05 +02:00
- Scoped storage no longer flips on while no activity is registered: whether legacy storage was preserved is fixed per install, so it's asked once. - Free space reads as unknown (-1), not 0 bytes, without an activity. - Strings from Java (file listings and info, crash history, the camera list, the input debug string) go through GetJavaString, so names with characters outside the BMP aren't mangled, and a null string can't crash the crash history. - Requests (inputbox, browse_*) whose command can't be delivered are failed instead of left waiting forever. - A font that fails to load uses the default typeface (-1) instead of font 0, and is retried later if there was no activity yet. Missing exception checks added in the text renderer. - Method IDs and the storage callbacks are looked up once in JNI_OnLoad. - Unknown permissions are logged correctly. Also removes what was left of Moga support, the unused backbuffer format and a stray MessageBox macro, and tidies System_GetCameraDeviceList. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
231 lines
7.9 KiB
C++
231 lines
7.9 KiB
C++
#include "ppsspp_config.h"
|
|
|
|
#include "Common/Log.h"
|
|
#include "Common/StringUtils.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_android.h"
|
|
|
|
#include "android/jni/app-android.h"
|
|
|
|
#if PPSSPP_PLATFORM(ANDROID) && !defined(__LIBRETRO__)
|
|
|
|
#include <jni.h>
|
|
|
|
// A new TextDrawer is created every time graphics are brought up, which on Android is every resume.
|
|
// The class stays valid for the life of the process, so hold one global ref to it, forever.
|
|
static jclass GetTextRendererClass(JNIEnv *env) {
|
|
static jclass cls;
|
|
if (!cls) {
|
|
const char *textRendererClassName = "org/ppsspp/ppsspp/TextRenderer";
|
|
jclass localClass = findClass(textRendererClassName);
|
|
if (localClass) {
|
|
cls = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
|
|
env->DeleteLocalRef(localClass);
|
|
} else {
|
|
env->ExceptionClear(); // ClassNotFoundException
|
|
ERROR_LOG(Log::G3D, "Failed to find class: '%s'", textRendererClassName);
|
|
}
|
|
}
|
|
return cls;
|
|
}
|
|
|
|
TextDrawerAndroid::TextDrawerAndroid(Draw::DrawContext *draw) : TextDrawer(draw) {
|
|
auto env = getEnv();
|
|
cls_textRenderer = GetTextRendererClass(env);
|
|
if (cls_textRenderer) {
|
|
method_allocFont = env->GetStaticMethodID(cls_textRenderer, "allocFont", "(Landroid/content/Context;Ljava/lang/String;)I");
|
|
method_freeAllFonts = env->GetStaticMethodID(cls_textRenderer, "freeAllFonts", "()V");
|
|
method_measureText = env->GetStaticMethodID(cls_textRenderer, "measureText", "(Ljava/lang/String;ID)I");
|
|
method_renderText = env->GetStaticMethodID(cls_textRenderer, "renderText", "(Ljava/lang/String;IDII)[I");
|
|
}
|
|
dpiScale_ = CalculateDPIScale();
|
|
|
|
INFO_LOG(Log::G3D, "Initializing TextDrawerAndroid with DPI scale %f", dpiScale_);
|
|
}
|
|
|
|
TextDrawerAndroid::~TextDrawerAndroid() {
|
|
// cls_textRenderer is shared and never released, see GetTextRendererClass.
|
|
ClearCache();
|
|
fontMap_.clear(); // size is precomputed using dpiScale_.
|
|
}
|
|
|
|
bool TextDrawerAndroid::IsReady() const {
|
|
return cls_textRenderer != nullptr && method_measureText != nullptr && method_renderText != nullptr;
|
|
}
|
|
|
|
void TextDrawerAndroid::SetOrCreateFont(const FontStyle &style) {
|
|
// We will only use the default font but just for consistency let's still involve
|
|
// the font name.
|
|
auto iter = fontMap_.find(style);
|
|
if (iter != fontMap_.end()) {
|
|
fontStyle_ = style;
|
|
return;
|
|
}
|
|
|
|
std::string filename = GetFilenameForFontStyle(style) + ".ttf";
|
|
// TextRenderer has no font -1, so it draws that with the default typeface.
|
|
int fontId = -1;
|
|
auto fontIter = allocatedFonts_.find(filename);
|
|
if (fontIter != allocatedFonts_.end()) {
|
|
fontId = fontIter->second;
|
|
} else {
|
|
auto env = getEnv();
|
|
JNILocalFrame frame(env);
|
|
jobject activity = Android_GetActivity(env);
|
|
if (activity) {
|
|
jstring jstr = env->NewStringUTF(filename.c_str());
|
|
fontId = env->CallStaticIntMethod(cls_textRenderer, method_allocFont, activity, jstr);
|
|
if (CheckJavaException(env, __FUNCTION__) || fontId < 0) {
|
|
ERROR_LOG(Log::G3D, "Failed to load font '%s', using the default", filename.c_str());
|
|
fontId = -1;
|
|
}
|
|
// Remembered even if it failed, that won't change.
|
|
allocatedFonts_[filename] = fontId;
|
|
} else {
|
|
// Not remembered, so it's tried again when the fonts are next recreated.
|
|
WARN_LOG(Log::G3D, "No activity to load font '%s' with, using the default", filename.c_str());
|
|
}
|
|
}
|
|
|
|
AndroidFontEntry entry{};
|
|
entry.font = fontId;
|
|
entry.size = 1.25f * style.sizePts / dpiScale_; // Not sure why this formula works.
|
|
|
|
// Just chose a factor that looks good, don't know what unit size is in anyway.
|
|
fontMap_[style] = entry;
|
|
fontStyle_ = style;
|
|
}
|
|
|
|
void TextDrawerAndroid::MeasureStringInternal(std::string_view str, float *w, float *h) {
|
|
auto iter = fontMap_.find(fontStyle_);
|
|
if (iter == fontMap_.end()) {
|
|
ERROR_LOG(Log::G3D, "Missing font");
|
|
*w = 1.0f;
|
|
*h = 1.0f;
|
|
return;
|
|
}
|
|
|
|
std::string text;
|
|
ConvertUTF8ToJavaModifiedUTF8(&text, str);
|
|
|
|
auto env = getEnv();
|
|
jstring jstr = env->NewStringUTF(text.c_str());
|
|
uint32_t size = env->CallStaticIntMethod(cls_textRenderer, method_measureText, jstr, iter->second.font, iter->second.size);
|
|
env->DeleteLocalRef(jstr);
|
|
if (CheckJavaException(env, __FUNCTION__)) {
|
|
*w = 1.0f;
|
|
*h = 1.0f;
|
|
return;
|
|
}
|
|
|
|
*w = (float)(size >> 16);
|
|
*h = (float)(size & 0xFFFF);
|
|
}
|
|
|
|
bool TextDrawerAndroid::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;
|
|
}
|
|
|
|
auto iter = fontMap_.find(fontStyle_);
|
|
if (iter == fontMap_.end()) {
|
|
ERROR_LOG(Log::G3D, "Missing font");
|
|
return false;
|
|
}
|
|
|
|
auto env = getEnv();
|
|
|
|
std::string text;
|
|
ConvertUTF8ToJavaModifiedUTF8(&text, str);
|
|
jstring jstr = env->NewStringUTF(text.c_str());
|
|
|
|
uint32_t textSize = env->CallStaticIntMethod(cls_textRenderer, method_measureText, jstr, iter->second.font, iter->second.size);
|
|
if (CheckJavaException(env, __FUNCTION__)) {
|
|
env->DeleteLocalRef(jstr);
|
|
return false;
|
|
}
|
|
int imageWidth = (short)(textSize >> 16);
|
|
int imageHeight = (short)(textSize & 0xFFFF);
|
|
if (imageWidth <= 0)
|
|
imageWidth = 1;
|
|
if (imageHeight <= 0)
|
|
imageHeight = 1;
|
|
// WARN_LOG(Log::G3D, "Text: '%.*s' (%02x)", (int)str.length(), str.data(), str[0]);
|
|
|
|
// TODO: Handle bold/italic
|
|
jintArray imageData = (jintArray)env->CallStaticObjectMethod(cls_textRenderer, method_renderText, jstr, iter->second.font, iter->second.size, imageWidth, imageHeight);
|
|
env->DeleteLocalRef(jstr);
|
|
// Most likely an OutOfMemoryError allocating the bitmap.
|
|
if (CheckJavaException(env, __FUNCTION__) || !imageData) {
|
|
ERROR_LOG(Log::G3D, "TextRenderer failed to render text");
|
|
return false;
|
|
}
|
|
if (env->GetArrayLength(imageData) != imageWidth * imageHeight) {
|
|
ERROR_LOG(Log::G3D, "TextRenderer returned bad image size");
|
|
env->DeleteLocalRef(imageData);
|
|
return false;
|
|
}
|
|
jint *jimage = env->GetIntArrayElements(imageData, nullptr);
|
|
if (!jimage) {
|
|
env->ExceptionClear(); // OutOfMemoryError
|
|
env->DeleteLocalRef(imageData);
|
|
return false;
|
|
}
|
|
|
|
entry.texture = nullptr;
|
|
entry.bmWidth = imageWidth;
|
|
entry.width = imageWidth;
|
|
entry.bmHeight = imageHeight;
|
|
entry.height = imageHeight;
|
|
entry.lastUsedFrame = frameCount_;
|
|
|
|
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++) {
|
|
uint32_t v = jimage[imageWidth * y + x];
|
|
// Premultiplied alpha: Put alpha in all four channels.
|
|
bitmapData16[entry.bmWidth * y + x] = AlphaToPremul4444(v >> 24);
|
|
}
|
|
}
|
|
} else if (texFormat == Draw::DataFormat::R8_UNORM) {
|
|
// We only get here if swizzle is possible, so we can duplicate the value to all four channels.
|
|
bitmapData.resize(entry.bmWidth * entry.bmHeight);
|
|
for (int y = 0; y < entry.bmHeight; y++) {
|
|
for (int x = 0; x < entry.bmWidth; x++) {
|
|
uint32_t v = jimage[imageWidth * y + x];
|
|
bitmapData[entry.bmWidth * y + x] = (uint8_t)(v >> 24);
|
|
}
|
|
}
|
|
} else if (texFormat == Draw::DataFormat::R8G8B8A8_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++) {
|
|
uint32_t v = jimage[imageWidth * y + x];
|
|
// Swap R and B, for some reason.
|
|
v = (v & 0xFF00FF00) | ((v >> 16) & 0xFF) | ((v << 16) & 0xFF0000);
|
|
bitmapData32[entry.bmWidth * y + x] = RGBAToPremul8888(v);
|
|
}
|
|
}
|
|
} else {
|
|
_assert_msg_(false, "Bad TextDrawer format");
|
|
}
|
|
env->ReleaseIntArrayElements(imageData, jimage, JNI_ABORT);
|
|
env->DeleteLocalRef(imageData);
|
|
return true;
|
|
}
|
|
|
|
void TextDrawerAndroid::ClearFonts() {
|
|
fontMap_.clear(); // size is precomputed using dpiScale_.
|
|
}
|
|
|
|
#endif
|