diff --git a/CMakeLists.txt b/CMakeLists.txt index 6985fa0f12..4d79c4444a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -867,6 +867,8 @@ add_library(native STATIC ext/native/gfx_es2/draw_text_win.h ext/native/gfx_es2/draw_text_qt.cpp ext/native/gfx_es2/draw_text_qt.h + ext/native/gfx_es2/draw_text_android.cpp + ext/native/gfx_es2/draw_text_android.h ext/native/gfx_es2/gpu_features.cpp ext/native/gfx_es2/gpu_features.h ext/native/gfx_es2/glsl_program.cpp diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index e5a85b6a44..7140e0b4a6 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -966,7 +966,7 @@ static bool ExecuteCommands() { break; default: - ERROR_LOG(SYSTEM, "Unsupported GE dump command: %d", cmd.type); + ERROR_LOG(SYSTEM, "Unsupported GE dump command: %d", (int)cmd.type); return false; } } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index cce054af0f..03c8f8a21a 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -676,7 +676,7 @@ void GameSettingsScreen::CreateViews() { systemSettings->Add(new ItemHeader(sy->T("General"))); -#ifdef __ANDROID__ +#if PPSSPP_PLATFORM(ANDROID) if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) { static const char *screenRotation[] = {"Auto", "Landscape", "Portrait", "Landscape Reversed", "Portrait Reversed"}; PopupMultiChoice *rot = systemSettings->Add(new PopupMultiChoice(&g_Config.iScreenRotation, co->T("Screen Rotation"), screenRotation, 0, ARRAY_SIZE(screenRotation), co->GetName(), screenManager())); diff --git a/UI/MiscScreens.cpp b/UI/MiscScreens.cpp index 2a3a927a4f..293bf9b4b3 100644 --- a/UI/MiscScreens.cpp +++ b/UI/MiscScreens.cpp @@ -345,16 +345,18 @@ NewLanguageScreen::NewLanguageScreen(const std::string &title) : ListPopupScreen continue; } -#ifndef _WIN32 - // ar_AE only works on Windows. + // We only support Arabic on platforms where we have support for the native text rendering + // APIs, as proper Arabic support is way too difficult to implement ourselves. +#if !(defined(USING_QT_UI) || PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(ANDROID)) if (tempLangs[i].name.find("ar_AE") != std::string::npos) { continue; } - // Farsi also only works on Windows. + if (tempLangs[i].name.find("fa_IR") != std::string::npos) { continue; } #endif + FileInfo lang = tempLangs[i]; langs_.push_back(lang); diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 64f2892074..29ab1ffeb4 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -28,6 +28,7 @@ #include "thread/threadutil.h" #include "file/zip_read.h" #include "input/input_state.h" +#include "input/keycodes.h" #include "profiler/profiler.h" #include "math/math_util.h" #include "net/resolve.h" @@ -49,7 +50,9 @@ #include "app-android.h" -static JNIEnv *jniEnvUI; +JNIEnv *jniEnvMain; +JNIEnv *jniEnvGraphics; +JavaVM *javaVM; enum { ANDROID_VERSION_GINGERBREAD = 9, @@ -530,7 +533,9 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init (JNIEnv *env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath, jstring jdataDir, jstring jexternalDir, jstring jlibraryDir, jstring jcacheDir, jstring jshortcutParam, jint jAndroidVersion, jstring jboard) { - jniEnvUI = env; + jniEnvMain = env; + env->GetJavaVM(&javaVM); + setCurrentThreadName("androidInit"); ILOG("NativeApp.init() -- begin"); @@ -653,6 +658,12 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) { // JavaEGL extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, jobject obj) { + // Need to get the local JNI env for the graphics thread. Used later in draw_text_android. + int res = javaVM->GetEnv((void **)&jniEnvGraphics, JNI_VERSION_1_6); + if (res != JNI_OK) { + ELOG("GetEnv failed: %d", res); + } + if (javaGL && !graphicsContext) { graphicsContext = new AndroidJavaEGLGraphicsContext(); } @@ -1019,6 +1030,12 @@ static void ProcessFrameCommands(JNIEnv *env) { extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(JNIEnv *env, jobject obj, jobject _surf) { ANativeWindow *wnd = ANativeWindow_fromSurface(env, _surf); + // Need to get the local JNI env for the graphics thread. Used later in draw_text_android. + int res = javaVM->GetEnv((void **)&jniEnvGraphics, JNI_VERSION_1_6); + if (res != JNI_OK) { + ELOG("GetEnv failed: %d", res); + } + WLOG("runEGLRenderLoop. display_xres=%d display_yres=%d", display_xres, display_yres); if (wnd == nullptr) { @@ -1093,6 +1110,7 @@ retry: graphicsContext = nullptr; renderLoopRunning = false; WLOG("Render loop function exited."); + jniEnvGraphics = nullptr; return true; } diff --git a/android/jni/app-android.h b/android/jni/app-android.h index bf842ac54a..04a849b1bc 100644 --- a/android/jni/app-android.h +++ b/android/jni/app-android.h @@ -1,8 +1,13 @@ #pragma once -#include "input/keycodes.h" +#include "ppsspp_config.h" -// Compatability we alias the keycodes -// since native's keycodes are based on -// android keycodes. -typedef enum _keycode_t AndroidKeyCodes; +#if PPSSPP_PLATFORM(ANDROID) + +#include + +extern JNIEnv *jniEnvMain; +extern JNIEnv *jniEnvGraphics; +extern JavaVM *javaVM; + +#endif diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index bb0513561a..f96d70713c 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -406,6 +406,7 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + TextRenderer.init(this); shuttingDown = false; registerCallbacks(); diff --git a/android/src/org/ppsspp/ppsspp/TextRenderer.java b/android/src/org/ppsspp/ppsspp/TextRenderer.java new file mode 100644 index 0000000000..8eff130e86 --- /dev/null +++ b/android/src/org/ppsspp/ppsspp/TextRenderer.java @@ -0,0 +1,62 @@ +package org.ppsspp.ppsspp; +import android.content.Context; +import android.graphics.*; +import android.util.Log; + +import java.nio.ByteBuffer; + +public class TextRenderer { + private static Paint p; + private static Paint bg; + private static Typeface robotoCondensed; + private static final String TAG = "TextRenderer"; + static { + p = new Paint(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG); + p.setColor(Color.WHITE); + bg = new Paint(); + bg.setColor(Color.BLACK); + } + public static void init(Context ctx) { + robotoCondensed = Typeface.createFromAsset(ctx.getAssets(), "Roboto-Condensed.ttf"); + if (robotoCondensed != null) { + Log.i(TAG, "Successfully loaded Roboto Condensed"); + p.setTypeface(robotoCondensed); + } else { + Log.e(TAG, "Failed to load Roboto Condensed"); + } + } + private static Point measure(String string, double textSize) { + Rect bound = new Rect(); + p.setTextSize((float)textSize); + p.getTextBounds(string, 0, string.length(), bound); + int w = bound.width(); + int h = (int)(p.descent() - p.ascent() + 2.0f); + // Round width up to even already here to avoid annoyances from odd-width 16-bit textures which + // OpenGL does not like - each line must be 4-byte aligned + w = (w + 5) & ~1; + Point p = new Point(); + p.x = w; + p.y = h; + return p; + } + public static int measureText(String string, double textSize) { + Point s = measure(string, textSize); + return (s.x << 16) | s.y; + } + public static int[] renderText(String string, double textSize) { + Point s = measure(string, textSize); + + int w = s.x; + int h = s.y; + + Bitmap bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bmp); + canvas.drawRect(0.0f, 0.0f, w, h, bg); + p.setColor(Color.WHITE); + canvas.drawText(string, 1, -p.ascent() + 1, p); + + int [] pixels = new int[w * h]; + bmp.getPixels(pixels, 0, w, 0, 0, w, h); + return pixels; + } +} diff --git a/ext/native/Android.mk b/ext/native/Android.mk index 785b68d8c4..089f49d192 100644 --- a/ext/native/Android.mk +++ b/ext/native/Android.mk @@ -74,7 +74,8 @@ LOCAL_SRC_FILES :=\ gfx_es2/gl3stub.c \ gfx_es2/draw_buffer.cpp.arm \ gfx_es2/draw_text.cpp.arm \ - gfx/GLStateCache.cpp.arm \ + gfx_es2/draw_text_android.cpp.arm \ + gfx/GLStateCache.cpp.arm \ gfx/gl_debug_log.cpp \ gfx/gl_lost_manager.cpp \ gfx/texture_atlas.cpp \ diff --git a/ext/native/gfx_es2/draw_text.cpp b/ext/native/gfx_es2/draw_text.cpp index 1a49c2f0ab..3666a765b7 100644 --- a/ext/native/gfx_es2/draw_text.cpp +++ b/ext/native/gfx_es2/draw_text.cpp @@ -8,6 +8,7 @@ #include "gfx_es2/draw_text.h" #include "gfx_es2/draw_text_win.h" #include "gfx_es2/draw_text_qt.h" +#include "gfx_es2/draw_text_android.h" TextDrawer::TextDrawer(Draw::DrawContext *draw) : draw_(draw) { // These probably shouldn't be state. @@ -41,11 +42,17 @@ float TextDrawer::CalculateDPIScale() { } TextDrawer *TextDrawer::Create(Draw::DrawContext *draw) { + TextDrawer *drawer = nullptr; #if defined(_WIN32) && !PPSSPP_PLATFORM(UWP) - return new TextDrawerWin32(draw); + drawer = new TextDrawerWin32(draw); #elif defined(USING_QT_UI) - return new TextDrawerQt(draw); -#else - return nullptr; + drawer = new TextDrawerQt(draw); +#elif PPSSPP_PLATFORM(ANDROID) + drawer = new TextDrawerAndroid(draw); #endif -} \ No newline at end of file + if (drawer && !drawer->IsReady()) { + delete drawer; + drawer = nullptr; + } + return drawer; +} diff --git a/ext/native/gfx_es2/draw_text.h b/ext/native/gfx_es2/draw_text.h index 186e43d900..482e3912b7 100644 --- a/ext/native/gfx_es2/draw_text.h +++ b/ext/native/gfx_es2/draw_text.h @@ -51,6 +51,7 @@ class TextDrawer { public: virtual ~TextDrawer(); + virtual bool IsReady() const { return true; } virtual uint32_t SetFont(const char *fontName, int size, int flags) = 0; virtual void SetFont(uint32_t fontHandle) = 0; // Shortcut once you've set the font once. void SetFontScale(float xscale, float yscale); @@ -71,7 +72,6 @@ protected: Draw::DrawContext *draw_; virtual void ClearCache() = 0; - virtual void RecreateFonts() = 0; // On DPI change void WrapString(std::string &out, const char *str, float maxWidth); int frameCount_; diff --git a/ext/native/gfx_es2/draw_text_android.cpp b/ext/native/gfx_es2/draw_text_android.cpp new file mode 100644 index 0000000000..9e229008f0 --- /dev/null +++ b/ext/native/gfx_es2/draw_text_android.cpp @@ -0,0 +1,302 @@ +#include "base/display.h" +#include "base/logging.h" +#include "base/stringutil.h" +#include "thin3d/thin3d.h" +#include "util/hash/hash.h" +#include "util/text/wrap_text.h" +#include "util/text/utf8.h" +#include "gfx_es2/draw_text.h" +#include "gfx_es2/draw_text_android.h" + +#include "android/jni/app-android.h" +#include + +#if PPSSPP_PLATFORM(ANDROID) + +#include + +TextDrawerAndroid::TextDrawerAndroid(Draw::DrawContext *draw) : TextDrawer(draw) { + env_ = jniEnvGraphics; + const char *textRendererClassName = "org/ppsspp/ppsspp/TextRenderer"; + jclass localClass = env_->FindClass(textRendererClassName); + cls_textRenderer = reinterpret_cast(env_->NewGlobalRef(localClass)); + ILOG("cls_textRender: %p", cls_textRenderer); + if (cls_textRenderer) { + method_measureText = env_->GetStaticMethodID(cls_textRenderer, "measureText", "(Ljava/lang/String;D)I"); + ILOG("method_measureText: %p", method_measureText); + method_renderText = env_->GetStaticMethodID(cls_textRenderer, "renderText", "(Ljava/lang/String;D)[I"); + ILOG("method_renderText: %p", method_renderText); + } else { + ELOG("Failed to find class: '%s'", textRendererClassName); + } + dpiScale_ = 1.0f; +} + +TextDrawerAndroid::~TextDrawerAndroid() { + // Not sure why we can't do this but it crashes. Likely some deeper threading issue. + // At worst we leak one ref... + // env_->DeleteGlobalRef(cls_textRenderer); + ClearCache(); +} + +bool TextDrawerAndroid::IsReady() const { + return cls_textRenderer != nullptr && method_measureText != nullptr && method_renderText != nullptr; +} + +uint32_t TextDrawerAndroid::SetFont(const char *fontName, int size, int flags) { + // We will only use the default font but just for consistency let's still involve + // the font name. + uint32_t fontHash = hash::Fletcher((const uint8_t *)fontName, strlen(fontName)); + fontHash ^= size; + fontHash ^= flags << 10; + + auto iter = fontMap_.find(fontHash); + if (iter != fontMap_.end()) { + fontHash_ = fontHash; + return fontHash; + } + + // Just chose a factor that looks good, don't know what unit size is in anyway. + AndroidFontEntry entry; + entry.size = (float)(size * 1.4f) / dpiScale_; + fontMap_[fontHash] = entry; + fontHash_ = fontHash; + return fontHash; +} + +void TextDrawerAndroid::SetFont(uint32_t fontHandle) { + uint32_t fontHash = fontHandle; + auto iter = fontMap_.find(fontHash); + if (iter != fontMap_.end()) { + fontHash_ = fontHandle; + } else { + ELOG("Invalid font handle %08x", fontHandle); + } +} + +std::string TextDrawerAndroid::NormalizeString(std::string str) { + return ReplaceAll(str, "&&", "&"); +} + +void TextDrawerAndroid::MeasureString(const char *str, size_t len, float *w, float *h) { + uint32_t stringHash = hash::Fletcher((const uint8_t *)str, len); + uint32_t entryHash = stringHash ^ fontHash_; + + TextMeasureEntry *entry; + auto iter = sizeCache_.find(entryHash); + if (iter != sizeCache_.end()) { + entry = iter->second.get(); + } else { + float scaledSize = 14; + auto iter = fontMap_.find(fontHash_); + if (iter != fontMap_.end()) { + scaledSize = iter->second.size; + } else { + ELOG("Missing font"); + } + std::string text(NormalizeString(std::string(str, len))); + jstring jstr = env_->NewStringUTF(text.c_str()); + uint32_t size = env_->CallStaticIntMethod(cls_textRenderer, method_measureText, jstr, scaledSize); + env_->DeleteLocalRef(jstr); + + entry = new TextMeasureEntry(); + entry->width = (size >> 16); + entry->height = (size & 0xFFFF); + sizeCache_[entryHash] = std::unique_ptr(entry); + } + entry->lastUsedFrame = frameCount_; + *w = entry->width * fontScaleX_ * dpiScale_; + *h = entry->height * fontScaleY_ * dpiScale_; +} + +void TextDrawerAndroid::MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align) { + double scaledSize = 14; + auto iter = fontMap_.find(fontHash_); + if (iter != fontMap_.end()) { + scaledSize = iter->second.size; + } else { + ELOG("Missing font"); + } + + std::string toMeasure = std::string(str, len); + if (align & FLAG_WRAP_TEXT) { + bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; + WrapString(toMeasure, toMeasure.c_str(), rotated ? bounds.h : bounds.w); + } + + std::vector lines; + SplitString(toMeasure, '\n', lines); + float total_w = 0.0f; + float total_h = 0.0f; + for (size_t i = 0; i < lines.size(); i++) { + uint32_t stringHash = hash::Fletcher((const uint8_t *)&lines[i][0], lines[i].length()); + uint32_t entryHash = stringHash ^ fontHash_; + + TextMeasureEntry *entry; + auto iter = sizeCache_.find(entryHash); + if (iter != sizeCache_.end()) { + entry = iter->second.get(); + } else { + std::string text(NormalizeString(lines[i])); + jstring jstr = env_->NewStringUTF(text.c_str()); + uint32_t size = env_->CallStaticIntMethod(cls_textRenderer, method_measureText, jstr, scaledSize); + env_->DeleteLocalRef(jstr); + int sizecx = size >> 16; + int sizecy = size & 0xFFFF; + entry = new TextMeasureEntry(); + entry->width = sizecx; + entry->height = sizecy; + sizeCache_[entryHash] = std::unique_ptr(entry); + } + entry->lastUsedFrame = frameCount_; + + if (total_w < entry->width * fontScaleX_) { + total_w = entry->width * fontScaleX_; + } + total_h += entry->height * fontScaleY_; + } + *w = total_w * dpiScale_; + *h = total_h * dpiScale_; +} + +void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align) { + using namespace Draw; + std::string text(NormalizeString(std::string(str))); + if (text.empty()) + return; + JNIEnv *env; + int result = javaVM->GetEnv((void **)&env, JNI_VERSION_1_6); + assert(env == env_); + + uint32_t stringHash = hash::Fletcher((const uint8_t *)text.data(), text.size()); + uint32_t entryHash = stringHash ^ fontHash_ ^ (align << 24); + + target.Flush(true); + + TextStringEntry *entry; + + auto iter = cache_.find(entryHash); + if (iter != cache_.end()) { + entry = iter->second.get(); + entry->lastUsedFrame = frameCount_; + draw_->BindTexture(0, entry->texture); + } else { + double size; + auto iter = fontMap_.find(fontHash_); + if (iter != fontMap_.end()) { + size = iter->second.size; + } else { + ELOG("Missing font"); + } + + jstring jstr = env_->NewStringUTF(text.c_str()); + uint32_t textSize = env_->CallStaticIntMethod(cls_textRenderer, method_measureText, jstr, size); + int imageWidth = (textSize >> 16); + int imageHeight = (textSize & 0xFFFF); + jintArray imageData = (jintArray)env_->CallStaticObjectMethod(cls_textRenderer, method_renderText, jstr, size); + env_->DeleteLocalRef(jstr); + + entry = new TextStringEntry(); + entry->bmWidth = entry->width = imageWidth; + entry->bmHeight = entry->height = imageHeight; + entry->lastUsedFrame = frameCount_; + + TextureDesc desc{}; + desc.type = TextureType::LINEAR2D; + desc.format = Draw::DataFormat::B4G4R4A4_UNORM_PACK16; + desc.width = entry->bmWidth; + desc.height = entry->bmHeight; + desc.depth = 1; + desc.mipLevels = 1; + + uint16_t *bitmapData = new uint16_t[entry->bmWidth * entry->bmHeight]; + jint* jimage = env_->GetIntArrayElements(imageData, nullptr); + int arraySize = env_->GetArrayLength(imageData); + assert(arraySize == imageWidth * imageHeight); + for (int x = 0; x < entry->bmWidth; x++) { + for (int y = 0; y < entry->bmHeight; y++) { + uint32_t v = jimage[imageWidth * y + x]; + v = 0xFFF0 | ((v >> 12) & 0xF); // Just grab some bits from the green channel. + bitmapData[entry->bmWidth * y + x] = (uint16_t)v; + } + } + env_->ReleaseIntArrayElements(imageData, jimage, 0); + desc.initData.push_back((uint8_t *)bitmapData); + entry->texture = draw_->CreateTexture(desc); + delete[] bitmapData; + cache_[entryHash] = std::unique_ptr(entry); + draw_->BindTexture(0, entry->texture); + } + float w = entry->bmWidth * fontScaleX_ * dpiScale_; + float h = entry->bmHeight * fontScaleY_ * dpiScale_; + DrawBuffer::DoAlign(align, &x, &y, &w, &h); + target.DrawTexRect(x, y, x + w, y + h, 0.0f, 0.0f, 1.0f, 1.0f, color); + target.Flush(true); +} + +void TextDrawerAndroid::ClearCache() { + for (auto &iter : cache_) { + if (iter.second->texture) + iter.second->texture->Release(); + } + cache_.clear(); + sizeCache_.clear(); +} + +void TextDrawerAndroid::DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) { + float x = bounds.x; + float y = bounds.y; + if (align & ALIGN_HCENTER) { + x = bounds.centerX(); + } else if (align & ALIGN_RIGHT) { + x = bounds.x2(); + } + if (align & ALIGN_VCENTER) { + y = bounds.centerY(); + } else if (align & ALIGN_BOTTOM) { + y = bounds.y2(); + } + + std::string toDraw = str; + if (align & FLAG_WRAP_TEXT) { + bool rotated = (align & (ROTATE_90DEG_LEFT | ROTATE_90DEG_RIGHT)) != 0; + WrapString(toDraw, str, rotated ? bounds.h : bounds.w); + } + + DrawString(target, toDraw.c_str(), x, y, color, align); +} + +void TextDrawerAndroid::OncePerFrame() { + frameCount_++; + // If DPI changed (small-mode, future proper monitor DPI support), drop everything. + float newDpiScale = CalculateDPIScale(); + if (newDpiScale != dpiScale_) { + ILOG("Scale changed - wiping cache"); + dpiScale_ = newDpiScale; + ClearCache(); + fontMap_.clear(); // size is precomputed using dpiScale_. + } + + // Drop old strings. Use a prime number to reduce clashing with other rhythms + if (frameCount_ % 23 == 0) { + for (auto iter = cache_.begin(); iter != cache_.end();) { + if (frameCount_ - iter->second->lastUsedFrame > 100) { + if (iter->second->texture) + iter->second->texture->Release(); + cache_.erase(iter++); + } else { + iter++; + } + } + + for (auto iter = sizeCache_.begin(); iter != sizeCache_.end(); ) { + if (frameCount_ - iter->second->lastUsedFrame > 100) { + sizeCache_.erase(iter++); + } else { + iter++; + } + } + } +} + +#endif diff --git a/ext/native/gfx_es2/draw_text_android.h b/ext/native/gfx_es2/draw_text_android.h new file mode 100644 index 0000000000..010cb8c8b5 --- /dev/null +++ b/ext/native/gfx_es2/draw_text_android.h @@ -0,0 +1,52 @@ +#pragma once + +#include "ppsspp_config.h" + +#include +#include "gfx_es2/draw_text.h" + +#if PPSSPP_PLATFORM(ANDROID) + +#include + +struct AndroidFontEntry { + double size; +}; + +class TextDrawerAndroid : public TextDrawer { +public: + TextDrawerAndroid(Draw::DrawContext *draw); + ~TextDrawerAndroid(); + + bool IsReady() const override; + uint32_t SetFont(const char *fontName, int size, int flags) override; + void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once. + void MeasureString(const char *str, size_t len, float *w, float *h) override; + void MeasureStringRect(const char *str, size_t len, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT) override; + void DrawString(DrawBuffer &target, const char *str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT) override; + void DrawStringRect(DrawBuffer &target, const char *str, const Bounds &bounds, uint32_t color, int align) override; + // Use for housekeeping like throwing out old strings. + void OncePerFrame() override; + +protected: + void ClearCache() override; + +private: + std::string NormalizeString(std::string str); + + // JNI functions + JNIEnv *env_; + jclass cls_textRenderer; + jmethodID method_measureText; + jmethodID method_renderText; + + uint32_t fontHash_; + + std::map fontMap_; + + // The key is the CityHash of the string xor the fontHash_. + std::map> cache_; + std::map> sizeCache_; +}; + +#endif diff --git a/ext/native/gfx_es2/draw_text_qt.cpp b/ext/native/gfx_es2/draw_text_qt.cpp index ad33e8168e..d0d8773dd8 100644 --- a/ext/native/gfx_es2/draw_text_qt.cpp +++ b/ext/native/gfx_es2/draw_text_qt.cpp @@ -15,7 +15,6 @@ #include #include - TextDrawerQt::TextDrawerQt(Draw::DrawContext *draw) : TextDrawer(draw) { } @@ -46,10 +45,6 @@ void TextDrawerQt::SetFont(uint32_t fontHandle) { } -void TextDrawerQt::RecreateFonts() { - -} - void TextDrawerQt::MeasureString(const char *str, size_t len, float *w, float *h) { QFont* font = fontMap_.find(fontHash_)->second; QFontMetrics fm(*font); @@ -177,7 +172,6 @@ void TextDrawerQt::OncePerFrame() { if (newDpiScale != dpiScale_) { dpiScale_ = newDpiScale; ClearCache(); - RecreateFonts(); } // Drop old strings. Use a prime number to reduce clashing with other rhythms diff --git a/ext/native/gfx_es2/draw_text_qt.h b/ext/native/gfx_es2/draw_text_qt.h index 2a24b7cad5..815a76a13a 100644 --- a/ext/native/gfx_es2/draw_text_qt.h +++ b/ext/native/gfx_es2/draw_text_qt.h @@ -23,7 +23,6 @@ public: protected: void ClearCache() override; - void RecreateFonts() override; // On DPI change uint32_t fontHash_; std::map fontMap_; diff --git a/ext/native/gfx_es2/draw_text_win.h b/ext/native/gfx_es2/draw_text_win.h index 7461afaf63..a8d35744b5 100644 --- a/ext/native/gfx_es2/draw_text_win.h +++ b/ext/native/gfx_es2/draw_text_win.h @@ -30,7 +30,7 @@ public: protected: void ClearCache() override; - void RecreateFonts() override; // On DPI change + void RecreateFonts(); // On DPI change TextDrawerContext *ctx_; std::map> fontMap_; diff --git a/ext/native/native.vcxproj b/ext/native/native.vcxproj index 12cd0e4b3d..34f5e2aeb8 100644 --- a/ext/native/native.vcxproj +++ b/ext/native/native.vcxproj @@ -237,6 +237,7 @@ + @@ -692,6 +693,7 @@ + diff --git a/ext/native/native.vcxproj.filters b/ext/native/native.vcxproj.filters index b1718027c9..ee8f08f807 100644 --- a/ext/native/native.vcxproj.filters +++ b/ext/native/native.vcxproj.filters @@ -320,6 +320,9 @@ gfx + + gfx + @@ -778,6 +781,9 @@ gfx + + gfx + diff --git a/ext/native/thin3d/thin3d_gl.cpp b/ext/native/thin3d/thin3d_gl.cpp index 584eaeb20d..7aeba44140 100644 --- a/ext/native/thin3d/thin3d_gl.cpp +++ b/ext/native/thin3d/thin3d_gl.cpp @@ -675,6 +675,7 @@ OpenGLTexture::OpenGLTexture(const TextureDesc &desc) { type_ = desc.type; target_ = TypeToTarget(desc.type); canWrap_ = isPowerOf2(width_) && isPowerOf2(height_); + mipLevels_ = desc.mipLevels; if (!desc.initData.size()) return; diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index dc31b63459..69c24fa1bc 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -675,6 +675,9 @@ enum class TextureState { }; bool VKTexture::Create(const TextureDesc &desc) { + // Zero-sized textures not allowed. + if (desc.width * desc.height * desc.depth == 0) + return false; format_ = desc.format; mipLevels_ = desc.mipLevels; width_ = desc.width;