v0.7.5b: first major portion of rewriting text rendering

some rough edges still need smoothing out
This commit is contained in:
Evan Debenham
2019-10-10 22:20:23 -04:00
parent bbee1a1372
commit b985ed3bf5
14 changed files with 393 additions and 214 deletions

View File

@@ -60,6 +60,10 @@ public class Texture {
}
}
public static void clear(){
bound_id = 0;
}
public void filter( int minMode, int maxMode ) {
bind();
Gdx.gl.glTexParameterf( Gdx.gl.GL_TEXTURE_2D, Gdx.gl.GL_TEXTURE_MIN_FILTER, minMode );

View File

@@ -105,7 +105,6 @@ public class Game implements ApplicationListener {
//refreshes texture and vertex data stored on the gpu
TextureCache.reload();
RenderedText.reloadCache();
Vertexbuffer.refreshAllBuffers();
}

View File

@@ -21,239 +21,212 @@
package com.watabou.noosa;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.graphics.Pixmap;
import com.watabou.gltextures.SmartTexture;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Affine2;
import com.badlogic.gdx.math.Matrix4;
import com.watabou.glwrap.Matrix;
import com.watabou.glwrap.Texture;
import com.watabou.utils.RectF;
import com.watabou.glwrap.Quad;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.nio.FloatBuffer;
import java.util.HashMap;
public class RenderedText extends Image {
private static Canvas canvas = new Canvas();
private static Paint painter = new Paint();
private static Typeface font;
//this is basically a LRU cache. capacity is determined by character count, not entry count.
//FIXME: Caching based on words is very inefficient for every language but chinese.
private static LinkedHashMap<String, CachedText> textCache = new LinkedHashMap<>(700, 0.75f, true);
private static int cachedChars = 0;
private static final int GC_TRIGGER = 1250;
private static final int GC_TARGET = 1000;
private static void runGC(){
Iterator<Map.Entry<String, CachedText>> it = textCache.entrySet().iterator();
while (cachedChars > GC_TARGET && it.hasNext()){
CachedText cached = it.next().getValue();
if (cached.activeTexts.isEmpty()) {
cachedChars -= cached.length;
cached.texture.delete();
it.remove();
}
}
}
private BitmapFont font = null;
private int size;
private String text;
private CachedText cache;
private boolean needsRender = false;
public RenderedText( ){
public RenderedText( ) {
text = null;
}
public RenderedText( int size ){
text = null;
this.size = size;
}
public RenderedText(String text, int size){
this.text = text;
this.size = size;
needsRender = true;
measure(this);
measure();
}
public void text( String text ){
this.text = text;
needsRender = true;
measure(this);
measure();
}
public String text(){
return text;
}
public void size( int size ){
this.size = size;
needsRender = true;
measure(this);
measure();
}
public float baseLine(){
return size * scale.y;
}
private static synchronized void measure(RenderedText r){
if ( r.text == null || r.text.equals("") ) {
r.text = "";
r.width=r.height=0;
r.visible = false;
private synchronized void measure(){
if (Thread.currentThread().getName().equals("SHPD Actor Thread")){
throw new RuntimeException("Text measured from the actor thread!");
}
if ( text == null || text.equals("") ) {
text = "";
width=height=0;
visible = false;
return;
} else {
r.visible = true;
visible = true;
}
painter.setTextSize(r.size);
painter.setAntiAlias(true);
if (font != null) {
painter.setTypeface(font);
} else {
painter.setTypeface(Typeface.DEFAULT);
}
//paint outer strokes
painter.setARGB(0xff, 0, 0, 0);
painter.setStyle(Paint.Style.STROKE);
painter.setStrokeWidth(r.size / 5f);
r.width = (painter.measureText(r.text)+ (r.size/5f));
r.height = (-painter.ascent() + painter.descent()+ (r.size/5f));
font = Game.platform.getFont(size, text);
GlyphLayout l = new GlyphLayout( font, text);
width = l.width;
//TODO this is almost the same as old height, but old height was clearly a bit off
height = size*1.375f;
//height = l.height - fonts.get(fontGenerator).get(size).getDescent() + size/5f;
}
private static synchronized void render(RenderedText r){
r.needsRender = false;
if (r.cache != null)
r.cache.activeTexts.remove(r);
String key = "text:" + r.size + " " + r.text;
if (textCache.containsKey(key)){
r.cache = textCache.get(key);
r.texture = r.cache.texture;
r.frame(r.cache.rect);
r.cache.activeTexts.add(r);
} else {
measure(r);
if (r.width == 0 || r.height == 0)
return;
//bitmap has to be in a power of 2 for some devices (as we're using openGL methods to render to texture)
Bitmap bitmap = Bitmap.createBitmap(Integer.highestOneBit((int)r.width)*2, Integer.highestOneBit((int)r.height)*2, Bitmap.Config.ARGB_4444);
bitmap.eraseColor(0x00000000);
canvas.setBitmap(bitmap);
canvas.drawText(r.text, (r.size/10f), r.size, painter);
//paint inner text
painter.setARGB(0xff, 0xff, 0xff, 0xff);
painter.setStyle(Paint.Style.FILL);
canvas.drawText(r.text, (r.size/10f), r.size, painter);
//FIXME really ugly and slow conversion between android bitmap and gdx pixmap
int[] pixels = new int[bitmap.getWidth()*bitmap.getHeight()];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
// Convert from ARGB to RGBA
for (int i = 0; i< pixels.length; i++) {
int pixel = pixels[i];
pixels[i] = (pixel << 8) | ((pixel >> 24) & 0xFF);
}
Pixmap pixmap = new Pixmap(bitmap.getWidth(), bitmap.getHeight(), Pixmap.Format.RGBA8888);
pixmap.getPixels().asIntBuffer().put(pixels);
r.texture = new SmartTexture(pixmap, Texture.NEAREST, Texture.CLAMP, true);
bitmap.recycle();
RectF rect = r.texture.uvRect(0, 0, r.width, r.height);
r.frame(rect);
r.cache = new CachedText();
r.cache.rect = rect;
r.cache.texture = r.texture;
r.cache.length = r.text.length();
r.cache.activeTexts = new HashSet<>();
r.cache.activeTexts.add(r);
cachedChars += r.cache.length;
textCache.put("text:" + r.size + " " + r.text, r.cache);
if (cachedChars >= GC_TRIGGER){
runGC();
}
}
}
@Override
protected void updateMatrix() {
super.updateMatrix();
//the y value is set at the top of the character, not at the top of accents.
Matrix.translate( matrix, 0, -Math.round((baseLine()*0.15f)/scale.y) );
//FIXME this doesn't work for .otf fonts on android 6.0
Matrix.translate( matrix, 0, Math.round((baseLine()*0.1f)/scale.y) );
}
private static TextRenderBatch textRenderer = new TextRenderBatch();
@Override
public void draw() {
if (needsRender)
render(this);
if (texture != null)
super.draw();
public synchronized void draw() {
updateMatrix();
TextRenderBatch.textBeingRendered = this;
font.draw(textRenderer, text, 0, 0);
}
@Override
public void destroy() {
if (cache != null)
cache.activeTexts.remove(this);
super.destroy();
}
public static void clearCache(){
for (CachedText cached : textCache.values()){
cached.texture.delete();
//implements regular PD rendering within a LibGDX batch so that our rendering logic
//can interface with the freetype font generator
private static class TextRenderBatch implements Batch {
//this isn't as good as only updating once, like with bitmaptext
// but it skips almost all allocations, which is almost as good
private static RenderedText textBeingRendered = null;
private static float[] vertices = new float[16];
private static HashMap<Integer, FloatBuffer> buffers = new HashMap<>();
@Override
public void draw(Texture texture, float[] spriteVertices, int offset, int count) {
Visual v = textBeingRendered;
FloatBuffer toOpenGL;
if (buffers.containsKey(count/20)){
toOpenGL = buffers.get(count/20);
toOpenGL.position(0);
} else {
toOpenGL = Quad.createSet(count / 20);
buffers.put(count/20, toOpenGL);
}
for (int i = 0; i < count; i += 20){
vertices[0] = spriteVertices[i+0];
vertices[1] = spriteVertices[i+1];
vertices[2] = spriteVertices[i+3];
vertices[3] = spriteVertices[i+4];
vertices[4] = spriteVertices[i+5];
vertices[5] = spriteVertices[i+6];
vertices[6] = spriteVertices[i+8];
vertices[7] = spriteVertices[i+9];
vertices[8] = spriteVertices[i+10];
vertices[9] = spriteVertices[i+11];
vertices[10] = spriteVertices[i+13];
vertices[11] = spriteVertices[i+14];
vertices[12] = spriteVertices[i+15];
vertices[13] = spriteVertices[i+16];
vertices[14] = spriteVertices[i+18];
vertices[15] = spriteVertices[i+19];
toOpenGL.put(vertices);
}
toOpenGL.position(0);
NoosaScript script = NoosaScript.get();
texture.bind();
com.watabou.glwrap.Texture.clear();
script.camera( v.camera() );
script.uModel.valueM4( v.matrix );
script.lighting(
v.rm, v.gm, v.bm, v.am,
v.ra, v.ga, v.ba, v.aa );
script.drawQuadSet( toOpenGL, count/20 );
}
cachedChars = 0;
textCache.clear();
}
public static void reloadCache(){
for (CachedText txt : textCache.values()){
txt.texture.reload();
}
}
public static void setFont(String asset){
if (asset == null) font = null;
else font = Typeface.createFromAsset(((AndroidApplication)Gdx.app).getAssets(), asset);
clearCache();
}
public static Typeface getFont(){
return font;
}
private static class CachedText{
public SmartTexture texture;
public RectF rect;
public int length;
public HashSet<RenderedText> activeTexts;
//none of these functions are needed, so they are stubbed
@Override
public void begin() { }
public void end() { }
public void setColor(Color tint) { }
public void setColor(float r, float g, float b, float a) { }
public Color getColor() { return null; }
public void setPackedColor(float packedColor) { }
public float getPackedColor() { return 0; }
public void draw(Texture texture, float x, float y, float originX, float originY, float width, float height, float scaleX, float scaleY, float rotation, int srcX, int srcY, int srcWidth, int srcHeight, boolean flipX, boolean flipY) { }
public void draw(Texture texture, float x, float y, float width, float height, int srcX, int srcY, int srcWidth, int srcHeight, boolean flipX, boolean flipY) { }
public void draw(Texture texture, float x, float y, int srcX, int srcY, int srcWidth, int srcHeight) { }
public void draw(Texture texture, float x, float y, float width, float height, float u, float v, float u2, float v2) { }
public void draw(Texture texture, float x, float y) { }
public void draw(Texture texture, float x, float y, float width, float height) { }
public void draw(TextureRegion region, float x, float y) { }
public void draw(TextureRegion region, float x, float y, float width, float height) { }
public void draw(TextureRegion region, float x, float y, float originX, float originY, float width, float height, float scaleX, float scaleY, float rotation) { }
public void draw(TextureRegion region, float x, float y, float originX, float originY, float width, float height, float scaleX, float scaleY, float rotation, boolean clockwise) { }
public void draw(TextureRegion region, float width, float height, Affine2 transform) { }
public void flush() { }
public void disableBlending() { }
public void enableBlending() { }
public void setBlendFunction(int srcFunc, int dstFunc) { }
public void setBlendFunctionSeparate(int srcFuncColor, int dstFuncColor, int srcFuncAlpha, int dstFuncAlpha) { }
public int getBlendSrcFunc() { return 0; }
public int getBlendDstFunc() { return 0; }
public int getBlendSrcFuncAlpha() { return 0; }
public int getBlendDstFuncAlpha() { return 0; }
public Matrix4 getProjectionMatrix() { return null; }
public Matrix4 getTransformMatrix() { return null; }
public void setProjectionMatrix(Matrix4 projection) { }
public void setTransformMatrix(Matrix4 transform) { }
public void setShader(ShaderProgram shader) { }
public ShaderProgram getShader() { return null; }
public boolean isBlendingEnabled() { return false; }
public boolean isDrawing() { return false; }
public void dispose() { }
}
}

View File

@@ -21,6 +21,8 @@
package com.watabou.utils;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
public abstract class PlatformSupport {
public abstract void updateDisplaySize();
@@ -35,5 +37,13 @@ public abstract class PlatformSupport {
public static abstract class TextCallback {
public abstract void onSelect( boolean positive, String text );
}
//TODO should consider spinning this into its own class, rather than platform support getting ever bigger
public abstract void setupFontGenerators(int pageSize, boolean systemFont );
public abstract void resetGenerators();
public abstract BitmapFont getFont(int size, String text);
}