Libgdx Rendering Bitmap font to pixmap texture causes extremely slow rendering - libgdx

I'm using libgdx scene2d to render 2d actors. Some of these actors originally included scene2d Label actors for rendering static text. The Labels work fine but drawing ~20 of them on the screen at once drops the frame rate by 10-15 frames, resulting in noticeably poor rendering while dragging.
I'm attempting to avoid the Labels by pre-drawing the text to textures, and rendering the textures as scene2d Image actors. I'm creating the texture using the code below:
BitmapFont font = manager.get(baseNameFont,BitmapFont.class);
GlyphLayout gl = new GlyphLayout(font,"Test Text");
int textWidth = (int)gl.width;
int textHeight = (int)gl.height;
LOGGER.info("textHeight: {}",textHeight);
//int width = Gdx.graphics.getWidth();
int width = textWidth;
//int height = 500;
int height = textHeight;
SpriteBatch spriteBatch = new SpriteBatch();
FrameBuffer m_fbo = new FrameBuffer(Pixmap.Format.RGB565, width,height, false);
m_fbo.begin();
Gdx.gl.glClearColor(1f,1f,1f,0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
Matrix4 normalProjection = new Matrix4()
.setToOrtho2D(0, 0, width, height);
spriteBatch.setProjectionMatrix(normalProjection);
spriteBatch.begin();
font.draw(spriteBatch,gl,0,height);
spriteBatch.end();//finish write to buffer
Pixmap pm = ScreenUtils.getFrameBufferPixmap(0, 0, (int) width, (int) height);//write frame buffer to Pixmap
m_fbo.end();
m_fbo.dispose();
m_fbo = null;
spriteBatch.dispose();
Texture texture = new Texture(pm);
textTexture = new TextureRegion(texture);
textTexture.flip(false,true);
manager.add(texture);
I assumed, and have read, that textures are often faster. However when I replaced the Labels with the texture, it had the same, if not worse, affect on the frame rate. Oddly, I'm not experiencing this when adding textures from a file, which makes me think I'm doing something wrong in my code. Is there a different way I should be pre-rendering these pieces of text?

I have not tested this, but I think you can enable culling for the whole Stage by setting its root view to use a cullingArea matching the world width and height of the viewport. I would do this in resize after updating the Stage Viewport just in case the update affects the world width and height of the viewport.
#Override
public void resize(int width, int height) {
//...
stage.getViewport().update(width, height, true);
stage.getRoot().setCullingArea(
new Rectangle(0f, 0f, stage.getViewport().getWorldWidth(), stage.getViewport().getWorldHeight())
);
}
It will only be able to cull Actors that have their x, y, width, and height set properly. This is true I think of anything in the scene2d UI package, but for your own custom Actors you will need to do it yourself.
If a child of your root view is a Group that encompasses more than the screen and many actors, you might want to cull its children, too, such that even if the group as a whole is not culled by the root view, the group can still cull a portion of its own children. To figure out the culling rectangle for this, I think you would intersect a rectangle of the group's size with the viewport's world size rectangle offset by -x and -y of the group's position (since the culling rectangle is relative to the position of the group it's set on).

Related

Libgdx get texture from Texture Atlas with findRegion

I have this code
textureAtlas = TextureAtlas("atlas.atlas")
val box = textureAtlas.findRegion("box")
I want to create a texture with "box". Is it possible? box.texture return the original texture, not the regioned. Oh and I don't want to use Sprite and SpriteBatch. I need this in 3D, not 2D.
Thanks
TextureAtlas actually not separating pieces. When you get region from atlas its just saying that this is the area you gonna use (u,v,u2,v2) and this is original reference to whole texture.
This is why batch.draw(Texture) and batch.draw(TextureRegion) are not same in use.
However taking part of picture as texture is possible.
You can use pixmap to do it.
First generate pixmap from atlas texture. Then create new empty pixmap in size of "box" area you want. Then assign pixel arrays and generate texture from your new pixmap.
It may be quite expensive due to your Textureatlas size.
You can use framebuffer.
Create FBbuilder and build new frame buffer.Draw texture region to this buffer and get texture from it.
Problem here is the sizes of texture will be same as viewport/screen sizes.I guess you can create new camera to change it to sizes you want.
GLFrameBuffer.FrameBufferBuilder frameBufferBuilder = new GLFrameBuffer.FrameBufferBuilder(widthofBox, heightofBox);
frameBufferBuilder.addColorTextureAttachment(GL30.GL_RGBA8, GL30.GL_RGBA, GL30.GL_UNSIGNED_BYTE);
frameBuffer = frameBufferBuilder.build();
OrthographicCamera c = new OrthographicCamera(widthofBox, heightofBox);
c.up.set(0, 1, 0);
c.direction.set(0, 0, -1);
c.position.set(widthofBox / 2, heightofBox / 2, 0f);
c.update();
batch.setProjectionMatrix(c.combined);
frameBuffer.begin();
batch.begin();
batch.draw(boxregion...)
batch.end();
frameBuffer.end();
Texture texturefbo = frameBuffer.getColorBufferTexture();
Texturefbo will be y flipped. You can fix this with texture draw method by setting scaleY to -1 or You can scale scaleY to -1 while drawing on framebuffer or can change camera like this
up.set(0, -1, 0);
direction.set(0, 0, 1);
to flip to camera on y axis.
Last thing came to my mind is mipmapping this texture.Its also not so hard.
texturefbo.bind();
Gdx.gl.glGenerateMipmap(GL20.GL_TEXTURE_2D);
texturefbo.setFilter(Texture.TextureFilter.MipMapLinearLinear,
Texture.TextureFilter.MipMapLinearLinear);
You can do this:
Texture boxTexture = new TextureRegion(textureAtlas.findRegion("box")).getTexture();

libgdx mesh screenshot original resolution

I have a mesh on which I have bound a texture in Render loop using texture.bind();
The original resolution of the texture is 1024 x 1024. My desktop screen resolution is 1366 x 768.
So I make some changes in the mesh triangles to modify the texture's look. I move some triangles here and there to get a final image. Now, I want to save that image in its original resolution, but If i do so using the image's original pixmap, it saves the original image before modification in 1024x1024. But I want to save the modified image in 1024x1024.
I also used the screenshot code:
byte[] pixels = ScreenUtils.getFrameBufferPixels(0, 0, Gdx.graphics.getBackBufferWidth(), Gdx.graphics.getBackBufferHeight(), true);
Pixmap pixmap = new Pixmap(Gdx.graphics.getBackBufferWidth(), Gdx.graphics.getBackBufferHeight(), Pixmap.Format.RGBA8888);
BufferUtils.copy(pixels, 0, pixmap.getPixels(), pixels.length);
PixmapIO.writePNG(Gdx.files.local("saved/screenshot.png"), pixmap);
pixmap.dispose();
But it saves the image in 1024x750 resolution because of the lower monitor resolution (getBackBufferHeight becomes 750 in this case).
I now want to capture/save the modified image(modified using mesh triangles). What exactly do I need to do now to get the modified 1024x1024 image? Should I use a ScrollPane to display the image in its original resolution so that when I take a screenshot, I get 1024x1024?
Do your modifications on a correctly sized frame buffer, instead of the screen's back buffer. But be wary of whether the device supports RGBA8888 for frame buffers (it's not required for OpenGL ES 2.0 so some GPUs don't support it).
int width = 1024;
int height = 1024;
FrameBuffer frameBuffer;
try {
frameBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, width, height, false);
} catch (GdxRuntimeException e) {
frameBuffer = new FrameBuffer(Pixmap.Format.RGB565, width, height, false);
}
frameBuffer.begin();
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//draw your texture and do your manipulations.
byte[] pixels = ScreenUtils.getFrameBufferPixels(0, 0, width, height, true);
Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);
BufferUtils.copy(pixels, 0, pixmap.getPixels(), pixels.length);
PixmapIO.writePNG(Gdx.files.local("saved/screenshot.png"), pixmap);
pixmap.dispose();
frameBuffer.end();
frameBuffer.dispose();

How to create a resolution "Independent" implementation of Stage for scene2d ui of LIBGDX

I have been trying to create a "main menu" screen with a single button.This button however, is 'unclickable' if a Viewport is set for the stage.If the viewport is not set it becomes clickable.The problem with second approach is however that the images "change size" depending on the resolution of devices.So they become smaller on higher resolution devices and vice versa.
Here is the code for your reference:
mainMenu(Game mainGame){
camera = new OrthographicCamera();
menuCam = new OrthographicCamera();
allVariables.Graphics_viewport_height = (allVariables.Graphics_viewport_width*Gdx.graphics.getHeight())/Gdx.graphics.getWidth();
camera.setToOrtho(false, allVariables.Graphics_viewport_width, allVariables.Graphics_viewport_height);
camera.update();
menuCam.setToOrtho(false,allVariables.Graphics_viewport_width,allVariables.Graphics_viewport_height);
menuCam.update();
maingame = mainGame;
batch = new SpriteBatch();
stage = new Stage();
stage.setViewport(new StretchViewport(allVariables.Graphics_viewport_width,allVariables.Graphics_viewport_height));
stage.getViewport().setCamera(menuCam);
table = new Table();
ImageButton.ImageButtonStyle playButtonStyle = new ImageButton.ImageButtonStyle();
playButtonStyle.imageDown = AllAssets.instance.skin.getDrawable("play_up");
playButtonStyle.imageUp = AllAssets.instance.skin.getDrawable("play_dn");
playButtonStyle.pressedOffsetY = 10;
playButtonStyle.pressedOffsetX = 10;
play = new ImageButton(playButtonStyle);
table.setFillParent(true);
table.align(Align.top);
table.add(play).minSize(100, 200);
table.debug();
stage.addActor(table);
Gdx.input.setInputProcessor(stage);}
The resize method :
public void resize(int width, int height) {
stage.getViewport().update(allVariables.Graphics_viewport_width,allVariables.Graphics_viewport_height,true);
}
What should be done to make it resolution independent so that the button works on different resolutions with same relative size that is not dependent on actual pixels.
So the answer to this question lies in the fact that there are Two specific size aspects of Viewports in Libgdx.The first is the "part of your game world" you want to show and the second is "the part of your screen" onto which you want to show your world(rather part of your world).
To tell Libgdx the what part of the world you wish to show you can use the constructor of the viewports.Like so:
new StretchViewport(worldViewportwidth,worldViewportheight)
Now in order to tell Libgdx about which part of your screen (partial part of the screen,full screen,et al) use the update method on the viewport:
public void resize(int width, int height) {
stage.getViewport().update(width,height,true);
}
Now, the stage should be able to accept input (if everything else is also correct) with the stage properly visible on different devices

Scaling up viewport

My game is 200 x 320 pixels and I want to scale this up to fit any screen. The problem is I need to scale this up in integer multiples of these dimensions so the upscaled pixels don't look uneven.
What should I put in my constructor, render() and resize() methods to achieve this?
In the constructor I now have:
camera = new OrthographicCamera();
camera.setToOrtho(false,200,320);
stage = new Stage(new ScalingViewport(Scaling.fill,200,320, camera));
In render():
camera.update();
game.batch.setProjectionMatrix(camera.combined);
And in resize():
int widthScale = width/200;
int heightScale = height/320;
int newScale = Math.max(widthScale, heightScale);
int multiplesWidth = newScale*200;
int multiplesHeight = newScale*320;
stage.getViewport().update(multiplesWidth, multiplesHeight, true);
camera.setToOrtho(false, 200, 320);
My problem is that the content isn't centered. How and where can I center whatever is on the screen? I tried with:
camera.position.set(Gdx.graphics.getWidth()/2f, Gdx.graphics.getHeight()/2f, 0);
but the screen is blank!
I think the problem is that you are trying to center the screen using the screen dimensions and not the world dimensions.
Try replacing
camera.position.set(Gdx.graphics.getWidth()/2f, Gdx.graphics.getHeight()/2f, 0);
with
camera.position.set(stage.getViewport().getWorldWidth()/2f, stage.getViewport().getWorldHeight()/2f, 0);
EDIT:
Okay, so here is an explanation why I proposed you set the camera to (width/2, height/2). (I will leave out the third z-Coordinate from now on as it will always be 0.)
camera.position.set() sets the center point of the camera. At (0,0) the camera is positioned right at the center of the screen, depicted by the red rectangle below. What libgdx does by default, and what I proposed offsets the camera by width/2(the blue line) and height/2(the green line) which translates to the magenta rectangle.
Side note: the reason camera.position.set(Gdx.graphics.getWidth()/2f, Gdx.graphics.getHeight()/2f, 0); didn't work is because Gdx.graphics.getX() returns the width and height of the window in pixels, not in the measurement system the Viewport uses. This offsets the camera about a meter up and to the right, where you obviously didn't draw anything.
If you want the red rectangle as camera, you can change the last argument of
stage.getViewport().update(multiplesWidth, multiplesHeight, true);
to false. Or use camera.position.set(0,0,0);

libGDX texture a 2d terrain

I work at a truck game with libgdx and box2d.
In my game 1 meter = 100 pixels.
My 2d terrain is generated by me, and is made by points.
What I did, is made a polygonregion for the whole polygon and used texturewrap.repeat.
The problem is that, my game size is scaled down by 100 times, to fit the box2d units.
So my camera width is 800 / 100 and height 480 / 100. (8x4.8 pixels)
How I created my polygon region
box = new Texture(Gdx.files.internal("box.png"));
box.setFilter(TextureFilter.Linear, TextureFilter.Linear);
box.setWrap(TextureWrap.Repeat, TextureWrap.Repeat);
TextureRegion region = new TextureRegion(box);
psb = new PolygonSpriteBatch();
float[] vertices = new float[paul.size];
for (int i = 0; i < paul.size; i++) {
vertices[i] = paul.get(i);
if (i % 2 == 1)
vertices[i] += 1f;
}
EarClippingTriangulator a = new EarClippingTriangulator();
ShortArray sar = a.computeTriangles(vertices);
short[] shortarray = new short[sar.size];
for (int i = 0; i < sar.size; i++)
shortarray[i] = sar.get(i);
PolygonRegion pr = new PolygonRegion(region, vertices, shortarray);
System.out.println(vertices.length + " " + shortarray.length);
ps = new PolygonSprite(pr);
Now i'll just draw the polygonsprite to my polygonsprite batch.
This will render the texture on the polygon repeatedly, but the picture will be 100 times bigger and is very streched.
The left example is the one that i want to make, and the right one is the way that my game looks..
This PR was merged which looks like it does what you want:
https://github.com/libgdx/libgdx/pull/3799
See RepeatablePolygonSprite.
I am not completely sure if this will solve your problem (can't test it right now), but you need to set the texture coordinates of your TextureRegion to a higher value, probably your factor of 100.
So you could try to use region.setU2(100) and region.setV2(100). Since Texture Coordinates usually go from [0,1], the values higher than that will be outside. And because you set the TextureWrap to repeat, this will then repeat your texture over and over.
This way, the TextureRegion will alredy show your one texture repeated 100 times in x and y direction. If you then tell the PolygonSprite to use that region, it should show it as in the image you posted.
Hope that helps... :)
You could create a new texture by code. Take your level size and fill it with your texture then delete to background the top side. Look at pixelmap. Maybe this will help you.
Edit:
TextureRegion doesn't repeat to fit the size you even use texture, or you use TiledDrawable.