I'm writing an application that has a custom text layout engine, and for this to work I must measure lots (thousands) of Strings using the .getTextBounds() method on the Paint class.
It does work. But on some devices, this measurement is extremely slow.
For example, measuring 10,000 5-6 chars long words
Samsung Galaxy S2 phone (or any other kind of device from the Samsung Galaxy series): 1.5 seconds
HTC Desire HD: ~90 seconds.
Asus Transformer TF300: ~30 seconds
Etc.
I've done method profiling and all I can see that the native Skia implementation of the measurement method takes much longer on some devices.
I'm doing nothing fancy during measurement, my code looks something like this:
Paint paint = new Paint();
Rect bounds = new Rect();
paint.setTextSize(22.0f);
for (int i = 0; i < input.length; i++) {
paint.getTextBounds(input[i], 0, input[i].length(), bounds);
}
Is there something to look out for when measuring text dimensions?
Did the manufacturers screw up something related to the font cache implementation in the ROM? Is there something I can do about this?
Related
We're using a d3.layout.force on a web app, and I've been investigating a bug report that it is sluggish on Android: it feels like the nodes are in oil, compared to how it works on desktop browsers, or iOS.
(By the way, we only ever have between 4 and 9 nodes, and the sluggishness does not feel different between 4 and 9.)
We set size(), linkDistance() and charge(); so we're using the defaults for friction, theta, alpha, gravity, etc. I experimented with these to try and reproduce the effect on desktop, but couldn't. (friction(0.67), instead of default of 0.9, was closest, but still felt different, somehow.)
I then set up an FPS meter (based on calls to the tick() function). We get 60fps on desktop, and it seems in the 40s and 50s on an ipad. But on Android Chrome (on a Nexus 7) it seems capped at 30fps, and is often half that. Android Firefox was in the 20s normally, but sometimes into the 30s.
So, is it a reasonable hypothesis that are Android devices are just slower? Could there be a cap of 30fps in Android Chrome?
Then how can I fix this? I believe d3.js uses requestAnimationFrame(). Often animation libraries take the time between calls to requestAnimationFrame() to decide how far to move objects (so when the CPU gets overloaded the animation becomes jerkier, but takes the same amount of time to complete). But it appears that d3.js does not do this, and moves everything the same amount by tick, not by elapsed time. What can I do about this?
(Ideally I'd like a solution based on how slow/fast the machine is, rather than having to sniff the browser.)
Curiously, adding more calls to force.tick() in my own requestAnimationFrame() handler (see https://stackoverflow.com/a/26189110/841830), does increase the FPS. That suggests it is not CPU bound, but instead a limit that Android is enforcing (perhaps to save battery?).
Here is the code I'm using, that tries to adapt dynamically to the current fps; it ain't beautiful but seems to be getting the job done in my test android devices, without changing the behaviour in iOS or desktop.
First, where you set up the force layout:
var ticksPerRender = 0;
var animStartTime,animFrameCount;
force.on('start',function start(){
animStartTime = new Date();animFrameCount=0;
});
requestAnimationFrame(function render() {
for(var i = 0;i < ticksPerRender;i++)force.tick();
if(force.alpha() > 0)requestAnimationFrame(render);
});
The above does two things:
sets up the fps counter
sets up our own animation callback, which does nothing by default (ticksPerRender starts off as zero).
Then at the end of your tick handler:
++animFrameCount;
if(animFrameCount>=15){ //Wait for 15, to get an accurate count
var now = new Date();
var fps = (animFrameCount / (now - animStartTime))*1000;
if(fps < 30){
ticksPerRender++;
animStartTime = now;animFrameCount = 0; //Reset the fps counter
}
if(fps > 60 && ticksPerRender >= 1){
ticksPerRender--;
animStartTime = now;animFrameCount = 0; //Reset the fps counter
}
}
This says that if the FPS is low (below 30), do an extra call to tick() on each animation frame. And if it goes high (over 60), remove that extra call.
Each time ticksPerRender is changed, we measure the FPS from scratch.
I have some problems with android.graphics.path.
I am developing a game. I have some Paths. They don't change there sizes, shapes. I draw them onto a canvas. I move them in every game's frame. So I set an offset: dx
It works fine on many devices:
Nokia X Dual Sim (Android 4.4.4 Cyanogenmode)(API level 19)
Samsung Galaxy Duos (GT-S7562)(Android 4.0.4)(API level 15)
Sony X8 (Android 2.3.7 Cyanogenmode)(API level 10)
and on some others
But it not works well on some other devices:
Samsung Galaxy Ace II (Android 4.1.2)(API level 16)
Nokia X Dual Sim (Nokia X platform 1.2)(Android Studio shows: API level 16)
and on some others
I set the offset in my draw method:
path.offset(dX, 0);
Not working means: it not moves. The system draws it to the original position.
But when it works, it works descent (moves, and it is fast)
I also tried with Matrixes:
translateMatrix = new Matrix();
translateMatrix.setTranslate(dX, 0);
path.transform(translateMatrix);
The same happened.
I see something on developer site of Android:
http://developer.android.com/reference/android/graphics/Path.html
void offset(float dx, float dy)
Offset the path by (dx,dy), returning true on success.
??? It must have changed, that's why here is an error
It may in connection with the Hardware Accelerated mode:
https://groups.google.com/forum/#!topic/android-developers/HgGVSbSghpk
I support from Api level 9, but I turned Harware Accelerated mode. (It works form API level 14)
The problem also shows up when I turn it off.
I don't see why is it sometimes work over API level 14, and sometimes why not?
It says, hardware accelerated mode supports Path:
http://android-developers.blogspot.de/2011/03/android-30-hardware-acceleration.html
Do you know what is the problem with this? What should I do?
Something that mixes it up more:
This works on every device (but works slower - it lags):
Canvas temp = new Canvas(bitmap);
path.draw(temp);
originalCanvas.drawBitmap(bitmap, 0, 0, myPaint);
So
I make a temporary canvas
I set it on a bitmap
I draw on that canvas (it will be on the bitmap)
I draw the bitmap onto the original canvas
Why does it work here?
Please help me how should I make it work.
I made it work, but not with the offset
I made my own offset function:
it saves the last position, and calculates the new one with the
offset.
Then it creates a new Path without using operator new
calculateNewPoints();
wallpath.rewind();
wallpath.moveTo(newPosX, newPosY);
.
.
.
wallpath.close();
It performs well :)
On API level 16 on manifest add the following attribute to the application
<application android:hardwareAccelerated="false" ...>
Indeed there seems to be a bug related to Path.offset() on older Android devices.
I have purchased an 8 years old "Samsung Galaxy S3" phone with Android 4.1.2 to test my word game and was surprised to discover, that Paths in the app are not filled:
My workaround is shown below, for older Android devices I do not use Path.offset() anymore, but instead Canvas.translate() and then draw the not offsetted Path object:
public class LetterTile extends RectF {
// consider anything older then Android 6.0 Marshmallow as an old device
public static final boolean TOO_OLD =
(Build.VERSION.SDK_INT < Build.VERSION_CODES.M);
private final Path mPathFill = new Path();
// mPathFillMoved is same as mPathFill, but offset by left, top
// it is used on newer devices to paint gradient over all tiles
private final Path mPathFillMoved = new Path();
public void draw(Canvas canvas, Paint gradientPaint) {
if (!TOO_OLD) {
// the mPathFillMoved only works on newer Android devices
canvas.drawPath(mPathFillMoved, gradientPaint);
}
canvas.save();
canvas.translate(left, top);
if (TOO_OLD) {
// on older Android devices just draw mPathFilled in plain yellow
canvas.drawPath(mPathFill, gradientPaint);
}
canvas.restore();
I'm developing an Android app that's going to work with bitmaps extensively and I'm looking for a reliable way to get the maximum texture size for OpenGL on different devices.
I know the minimum size = 2048x2048, but that's not good enough since there are already tablets out there with much higher resolutions (2560x1600 for example)
So is there a reliable way to get this information?
So far I've tried:
Canvas.getMaximumBitmapWidth() (Returns 32766, instead of 2048)
GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE ...) (Returns 0)
I'm working with minimum-sdk = 15 (ICS) and I'm testing it on a Asus Transformer TF700t Infinity
Does anyone know another way to get it?
Or will I have to compile a list of known GPUs with their max canvas size?
try using this code
int[] maxTextureSize = new int[1];
GLES10.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSize, 0);
maxTextureSize stores the size limit for decoded image such as 4096x4096, 8192x8192 . Remember to run this piece of code in the MainThread or you will get Zero.
This will give you the maximum height allowed.
Canvas canvas = new Canvas();
canvas.getMaximumBitmapHeight() / 8
I just wrote Conway's Game of Life in Java and i wanted to test my application on my android phone (Nexus 4).
For testing, i draw a BitmapFont on my SpriteBatch showing me FPS, gyro, cells alive and other data.
On the PC, i have ~ 500 to 4000 FPS, depending on how fast my GOL-logic works. However, if i start it on android, the FPS drop to 10-15. If I turn off my ShapeRenderer (which is my main Class to render all the GOL-Rectangles) with the GOL-logic at almost full-speed, the FPS is constantly at 60 (i guess you can't turn off VSync on a Nexus 4).
So, here is my question:
Why is the ShapeRenderer so highly inefficient? What else should i use to render my Shapes?
I will post my (fairly simple) render code and a picture:
this.setColor(new Color(1, 0, 0, 1));
for (int i = 0; i < grid.getX_length(); i++) {
for (int j = 0; j < grid.getY_length(); j++) {
if (cells[i][j].getState()){ // checks if the cell is dead or alive
this.setColor(new Color (0,0,0,1));
rect(i * size, j * size, size-gap, size-gap); // draws it.
}
(http://s14.directupload.net/images/140929/qc3uu8ew.png)
I don't know about the rectangles but I experienced the same Probelem with Circles. The Reoson that it takes so much time to draw a circle is that it draws a lot of single lines to form a circle.
See the source code for reference:
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/glutils/ShapeRenderer.java#L833
I'm working on a custom view for an android application, similar to the Analog Gauge sample code available from Mind the Robot.
Running the code from listed site, I get see this on my screen:
(Motorola Droid, 2.2.3), (Emulator, 4.0.3)
(Xoom, 4.0.3)(Other phone, 4.0.3)
The hand is missing!
The drawing calls are being made (I can see them in logcat), but the canvas elements the calls draw are invisible.
It's not API level dependent, though; if I import it the right way into a project, it will hand will show up when I run it on the Xoom.
But, when I move the files to a different project folder (same source code, same layouts) it goes back to missing the dial.
What's going on? How could the same code be producing such different outcomes on different devices?
So, the key clue in my mystery seemed to be that it worked on the emulator, but not on the hardware devices.
Hardware Rendering
I did peruse the hardware rendering page on the Android Developer's website, but apparently not closely enough.
http://developer.android.com/guide/topics/graphics/hardware-accel.html
While it does mention that the API's are available beginning version 11, it does not say that Hardware Rendering is turned on for all applications by default, starting with API Level 14 (ICS).
What does this mean for us?
Almost everything is faster; except for the few things that don't work.
I managed to violate two of these, without realizing it:
Canvas.DrawTextOnPath()
Paint.setShadowLayer()
It's not mentioned in the API reference (or anywhere else I can find, and certainly not checked by Lint), but using any of the listed operations can do weird things.
In my case, Canvas.DrawTextOnPath() seemed to work just fine.
But when Android notice that the paint that I used on the hand had shadow layer set, it silently ignored it.
How do I know if my View is hardware accelerated?
From the documentation link above:
There are two different ways to check whether the application is hardware accelerated:
View.isHardwareAccelerated() returns true if the View is attached to a hardware accelerated window.
Canvas.isHardwareAccelerated() returns true if the Canvas is hardware accelerated
If you must do this check in your drawing code, use Canvas.isHardwareAccelerated() instead >of View.isHardwareAccelerated() when possible. When a view is attached to a hardware >accelerated window, it can still be drawn using a non-hardware accelerated Canvas. This >happens, for instance, when drawing a view into a bitmap for caching purposes.
In my case, the opposite appears to have occurred.
The custom view logs that it is not Hardware-accelerated; however, the canvas reports that it is hardware-accelerated.
Work Arounds and Fixings
The simplest fix is forcing the custom view to do software rendering. Per the documentation this can be accomplished by:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Alternatively, you could remove the offending operations, and keep hardware rendering turned on.
Learn from my misfortune. Good luck, all.
I put it into init() and worked fine after that.
private void init() {
setLayerType(myView.LAYER_TYPE_SOFTWARE, null);
....
}
With myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); suggestion I can see hand. But I have still a problem: I see scale with only 0 written! As in the picture and two strage zeros out of the schema: (GALAXY NEXUS 4.2.1)
My drawScale() method is as in the example:
private void drawScale(Canvas canvas) {
canvas.drawOval(scaleRect, scalePaint);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
for (int i = 0; i < totalNicks; ++i) {
float y1 = scaleRect.top;
float y2 = y1 - 0.020f;
canvas.drawLine(0.5f, y1, 0.5f, y2, scalePaint);
if ((i % 5) == 0) {
int value = nickToDegree(i);
if ((value >= minDegrees) && (value <= maxDegrees)) {
String valueString = Integer.toString(value);
canvas.drawText(valueString, 0.5f, y2 - 0.015f, scalePaint);
}
}
canvas.rotate(degreesPerNick, 0.5f, 0.5f);
}
canvas.restore();
}
in my case i made this:
AnalogView bar = (AnalogView) findViewById(R.id.AnalogBar);
bar.setLayerType(bar.LAYER_TYPE_SOFTWARE, null);
if (value_list.size()>0) bar.SetData(Double.parseDouble(value_list.get(value_list.size()-1)));
where SetData in AnalogView is
public void SetData(double data) {
setHandTarget((float)data);
invalidate();
}
On Galaxy S4 Android 4.4.2
TYPE_TEMPERATURE is deprecated
use
TYPE_AMBIENT_TEMPERATURE
For anyone having problems with text drawing on scale in the initialisation do this:
scalePaint.setLinearText(true);