diff --git a/extensions/gdx-freetype/src/com/badlogic/gdx/graphics/g2d/freetype/FreeTypeFontGenerator.java b/extensions/gdx-freetype/src/com/badlogic/gdx/graphics/g2d/freetype/FreeTypeFontGenerator.java index b6a7c81448f..4770322e960 100755 --- a/extensions/gdx-freetype/src/com/badlogic/gdx/graphics/g2d/freetype/FreeTypeFontGenerator.java +++ b/extensions/gdx-freetype/src/com/badlogic/gdx/graphics/g2d/freetype/FreeTypeFontGenerator.java @@ -30,6 +30,7 @@ import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.BitmapFont.BitmapFontData; import com.badlogic.gdx.graphics.g2d.BitmapFont.Glyph; +import com.badlogic.gdx.graphics.g2d.GlyphLayout.GlyphRun; import com.badlogic.gdx.graphics.g2d.PixmapPacker; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.graphics.g2d.freetype.FreeType.Bitmap; @@ -61,7 +62,8 @@ * and have to be disposed as usual. * * @author mzechner - * @author Nathan Sweet */ + * @author Nathan Sweet + * @author Rob Rendell */ public class FreeTypeFontGenerator implements Disposable { static public final String DEFAULT_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890\"!`?'.,;:()[]{}<>|/@\\^$-%+=#_&~*\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F\u00A0\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF"; @@ -143,8 +145,7 @@ public BitmapFont generateFont (int size, String characters, boolean flip) { } /** Generates a new {@link BitmapFont}. The size is expressed in pixels. Throws a GdxRuntimeException if the font could not be - * generated. Using big sizes might cause such an exception. All characters need to fit onto a single texture. - * + * generated. Using big sizes might cause such an exception. * @param size the size of the font in pixels * @deprecated use {@link #generateFont(FreeTypeFontParameter)} instead */ public BitmapFont generateFont (int size) { @@ -156,14 +157,13 @@ public BitmapFont generateFont (FreeTypeFontParameter parameter) { } /** Generates a new {@link BitmapFont}. The size is expressed in pixels. Throws a GdxRuntimeException if the font could not be - * generated. Using big sizes might cause such an exception. All characters need to fit onto a single texture. - * + * generated. Using big sizes might cause such an exception. * @param parameter configures how the font is generated */ public BitmapFont generateFont (FreeTypeFontParameter parameter, FreeTypeBitmapFontData data) { generateData(parameter, data); if (data.regions == null && parameter.packer != null) { - data.regions = new Array(); - parameter.packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps, false); + data.regions = new Array(); + parameter.packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps); } BitmapFont font = new BitmapFont(data, data.regions, false); font.setOwnsTexture(parameter.packer == null); @@ -436,8 +436,8 @@ public FreeTypeBitmapFontData generateData (FreeTypeFontParameter parameter, Fre // Generate texture regions. if (ownsAtlas) { - data.regions = new Array(); - packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps, true); + data.regions = new Array(); + packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps); } return data; @@ -531,18 +531,15 @@ Glyph createGlyph (char c, FreeTypeBitmapFontData data, FreeTypeFontParameter pa } - Rectangle rect = packer.packDirectToTexture(mainPixmap); + Rectangle rect = packer.pack(mainPixmap); - // determine which page it was packed into - glyph.page = packer.getPages().size - 1; + glyph.page = packer.getPages().size - 1; // Glyph is always packed into the last page for now. glyph.srcX = (int)rect.x; glyph.srcY = (int)rect.y; - // If the data already has regions then this glyph is being added incrementally. - // Create a new texture for the new glyph if necessary. - if (data.regions != null && data.regions.size <= glyph.page) { - packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps, false); - } + // If a page was added, create a new texture region for the incrementally added glyph. + if (parameter.incremental && data.regions != null && data.regions.size <= glyph.page) + packer.updateTextureRegions(data.regions, parameter.minFilter, parameter.magFilter, parameter.genMipMaps); mainPixmap.dispose(); mainGlyph.dispose(); @@ -604,7 +601,7 @@ public Glyph getGlyph (char ch) { if (glyph == null) return null; setGlyph(ch, glyph); - setGlyphRegion(glyph, regions.get(glyph.page)); + setGlyphRegion(glyph, regions.get(glyph.page), packer.getPageWidth(), packer.getPageHeight()); glyphs.add(glyph); if (parameter.kerning) { @@ -625,6 +622,11 @@ public Glyph getGlyph (char ch) { return glyph; } + public void getGlyphs (GlyphRun run, CharSequence str, int start, int end) { + if (packer != null) packer.setPackToTexture(true); // All glyphs added after this are packed directly to the texture. + super.getGlyphs(run, str, start, end); + } + @Override public void dispose () { if (stroker != null) stroker.dispose(); diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/BitmapFont.java b/gdx/src/com/badlogic/gdx/graphics/g2d/BitmapFont.java index 0a6fdaa0996..9f8ff2f1eff 100644 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/BitmapFont.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/BitmapFont.java @@ -186,7 +186,8 @@ private void load (BitmapFontData data) { throw new IllegalArgumentException("BitmapFont texture region array cannot contain null elements."); } - data.setGlyphRegion(glyph, region); + Texture texture = region.getTexture(); + data.setGlyphRegion(glyph, region, texture.getWidth(), texture.getHeight()); } } } @@ -631,9 +632,9 @@ public void load (FileHandle fontFile, boolean flip) { } } - public void setGlyphRegion (Glyph glyph, TextureRegion region) { - float invTexWidth = 1.0f / region.getTexture().getWidth(); - float invTexHeight = 1.0f / region.getTexture().getHeight(); + public void setGlyphRegion (Glyph glyph, TextureRegion region, int textureWidth, int textureHeight) { + float invTexWidth = 1.0f / textureWidth; + float invTexHeight = 1.0f / textureHeight; float offsetX = 0, offsetY = 0; float u = region.u; diff --git a/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java b/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java index f22d43b1612..760ee7e656a 100755 --- a/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java +++ b/gdx/src/com/badlogic/gdx/graphics/g2d/PixmapPacker.java @@ -29,16 +29,12 @@ import com.badlogic.gdx.utils.GdxRuntimeException; import com.badlogic.gdx.utils.OrderedMap; -/** Packs {@link Pixmap} instances into one more more {@link Page} instances to generate an atlas of Pixmap instances. Provides - * means to directly convert the pixmap atlas to a {@link TextureAtlas}. The packer supports padding and border pixel duplication, - * specified during construction. The packer supports incremental inserts and updates of TextureAtlases generated with this - * class.

- * - * All methods except {@link #getPage(String)} and {@link #getPages()} are thread safe. The methods - * {@link #generateTextureAtlas(TextureFilter, TextureFilter, boolean)} and - * {@link #updateTextureAtlas(TextureAtlas, TextureFilter, TextureFilter, boolean)} need to be called on the rendering thread, all - * other methods can be called from any thread.

- * +/** Packs {@link Pixmap pixmaps} into one or more {@link Page pages} to generate an atlas of pixmap instances. Provides means to + * directly convert the pixmap atlas to a {@link TextureAtlas}. The packer supports padding and border pixel duplication, + * specified during construction. The packer supports incremental inserts and updates of TextureAtlases generated with this class. + *

+ * All methods can be called from any thread unless otherwise noted. + *

* One-off usage: * *

@@ -46,28 +42,33 @@
  * PixmapPacker packer = new PixmapPacker(512, 512, Format.RGB565, 2, true);
  * packer.pack("First Pixmap", pixmap1);
  * packer.pack("Second Pixmap", pixmap2);
- * TextureAtlas atlas = packer.generateTextureAtlas(TextureFilter.Nearest, TextureFilter.Nearest);
+ * TextureAtlas atlas = packer.generateTextureAtlas(TextureFilter.Nearest, TextureFilter.Nearest, false);
+ * packer.dispose();
+ * // ...
+ * atlas.dispose();
  * 
* - * Note that you should not dispose the packer in this usage pattern. Instead, dispose the TextureAtlas if no longer needed. + * With this usage pattern, disposing the packer will not dispose any pixmaps used by the texture atlas. The texture atlas must + * also be disposed when no longer needed. * - * Incremental usage: + * Incremental texture atlas usage: * *
  * // 512x512 pixel pages, RGB565 format, 2 pixels of padding, no border duplication
  * PixmapPacker packer = new PixmapPacker(512, 512, Format.RGB565, 2, false);
- * TextureAtlas incrementalAtlas = new TextureAtlas();
+ * TextureAtlas atlas = new TextureAtlas();
  * 
  * // potentially on a separate thread, e.g. downloading thumbnails
  * packer.pack("thumbnail", thumbnail);
  * 
  * // on the rendering thread, every frame
- * packer.updateTextureAtlas(incrementalAtlas, TextureFilter.Linear, TextureFilter.Linear);
+ * packer.updateTextureAtlas(atlas, TextureFilter.Linear, TextureFilter.Linear, false);
  * 
  * // once the atlas is no longer needed, make sure you get the final additions. This might
  * // be more elaborate depending on your threading model.
- * packer.updateTextureAtlas(incrementalAtlas, TextureFilter.Linear, TextureFilter.Linear);
- * incrementalAtlas.dispose();
+ * packer.updateTextureAtlas(atlas, TextureFilter.Linear, TextureFilter.Linear, false);
+ * // ...
+ * atlas.dispose();
  * 
* * Pixmap-only usage: @@ -79,165 +80,72 @@ * * // do something interesting with the resulting pages * for (Page page : packer.getPages()) { + * // ... * } * - * // dispose of the packer in this case * packer.dispose(); - * */ + * + * @author mzechner + * @author Nathan Sweet + * @author Rob Rendell */ public class PixmapPacker implements Disposable { + static private final boolean debug = false; + static private final String ANONYMOUS = "ANONYMOUS"; - static final class Node { - public Node leftChild; - public Node rightChild; - public Rectangle rect; - public String leaveName; - - public Node (int x, int y, int width, int height, Node leftChild, Node rightChild, String leaveName) { - this.rect = new Rectangle(x, y, width, height); - this.leftChild = leftChild; - this.rightChild = rightChild; - this.leaveName = leaveName; - } - - public Node () { - rect = new Rectangle(); - } - } - - public class Page { - Node root; - OrderedMap rects; - Pixmap image; - Texture texture; - final Array addedRects = new Array(); - boolean textureNeedsRefresh = false; - - public Pixmap getPixmap () { - return image; - } - - public OrderedMap getRects () { - return rects; - } - } - - private static final String ANONYMOUS = "ANONYMOUS"; - - final int pageWidth; - final int pageHeight; + final int pageWidth, pageHeight; final Format pageFormat; final int padding; final boolean duplicateBorder; final Array pages = new Array(); - Page currPage; + Page current; + boolean packToTexture; boolean disposed; - /**

- * Creates a new ImagePacker which will insert all supplied images into a width by height image. - * padding specifies the minimum number of pixels to insert between images. border will duplicate the - * border pixels of the inserted images to avoid seams when rendering with bi-linear filtering on. - *

- * - * @param width the width of the output image - * @param height the height of the output image - * @param padding the number of padding pixels - * @param duplicateBorder whether to duplicate the border */ - public PixmapPacker (int width, int height, Format format, int padding, boolean duplicateBorder) { - this.pageWidth = width; - this.pageHeight = height; - this.pageFormat = format; + /** Creates a new ImagePacker which will insert all supplied pixmaps into one or more pageWidth by + * pageHeight pixmaps. + * @param padding the number of blank pixels to insert between pixmaps. + * @param duplicateBorder duplicate the border pixels of the inserted images to avoid seams when rendering with bi-linear + * filtering on. */ + public PixmapPacker (int pageWidth, int pageHeight, Format pageFormat, int padding, boolean duplicateBorder) { + this.pageWidth = pageWidth; + this.pageHeight = pageHeight; + this.pageFormat = pageFormat; this.padding = padding; this.duplicateBorder = duplicateBorder; newPage(); } - /**

- * Inserts the given {@link Pixmap} with no name. You must use the returned Rectangle to later access the - * Pixmap's location in the appropriate page (which will be the last page in the PixmapPacker at the time this - * method returns). - *

- * - * @param image the image - * @return Rectangle describing the area the pixmap was rendered to. - * @throws RuntimeException in case the image did not fit due to the page size being too small */ - public synchronized Rectangle pack(Pixmap image) { - return pack(null, image, false); + /** Inserts the pixmap without a name. It cannot be looked up by name. + * @see #pack(String, Pixmap) */ + public synchronized Rectangle pack (Pixmap image) { + return pack(null, image); } - /**

- * Inserts the given {@link Pixmap}. You can later retrieve the image's position in the output image via the supplied name - * and the method {@link #getRect(String)}. - *

- * - * @param name the name of the image. This can be null, in which case the image is packed anonymously (see {@link #pack(Pixmap)}). - * @param image the image + /** Inserts the pixmap. If name was not null, you can later retrieve the image's position in the output image via + * {@link #getRect(String)}. + * @param name If null, the image cannot be looked up by name. * @return Rectangle describing the area the pixmap was rendered to. - * @throws RuntimeException in case the image did not fit due to the page size being too small or providing a duplicate name */ + * @throws GdxRuntimeException in case the image did not fit due to the page size being too small or providing a duplicate + * name. */ public synchronized Rectangle pack (String name, Pixmap image) { - return pack(name, image, false); - } - - /**

- * Inserts the given {@link Pixmap} with no name. You must use the returned Rectangle to later access the - * Pixmap's location in the appropriate page (which will be the last page in the PixmapPacker at the time this - * method returns). - *

- *

- * The added pixmap will be directly added to the texture on the graphics card if it has already been generated - * (e.g. if a TextureAtlas or TextureRegions have been built using this packer). This is more efficient for small - * numbers of small Pixmaps. If you are packing large Pixmaps or many Pixmaps, use {@link #pack(Pixmap)} followed - * by generating or updating a TextureAtlas instead. - *

- * - * @param image the image - * @return Rectangle describing the area the pixmap was rendered to. - * @throws RuntimeException in case the image did not fit due to the page size being too small */ - public synchronized Rectangle packDirectToTexture(Pixmap image) { - return pack(null, image, true); - } - - /**

- * Inserts the given {@link Pixmap}. You can later retrieve the image's position in the output image via the supplied name - * and the method {@link #getRect(String)}. - *

- *

- * The added pixmap will be directly added to the texture on the graphics card if it has already been generated - * (e.g. if a TextureAtlas or TextureRegions have been built using this packer). This is more efficient for small - * numbers of small Pixmaps. If you are packing large Pixmaps or many Pixmaps, use {@link #pack(String, Pixmap)} - * followed by generating or updating a TextureAtlas instead. - *

- * - * @param name the name of the image. This can be null, in which case the image is packed anonymously (see {@link #packDirectToTexture(Pixmap)}). - * @param image the image - * @return Rectangle describing the area the pixmap was rendered to. - * @throws RuntimeException in case the image did not fit due to the page size being too small or providing a duplicate name */ - public synchronized Rectangle packDirectToTexture(String name, Pixmap image) { - return pack(name, image, true); - } - - private Rectangle pack (String name, Pixmap image, boolean directToTexture) { if (disposed) return null; - if (name != null && getRect(name) != null) throw new RuntimeException("Key with name '" + name + "' is already in map"); - int borderPixels = padding + (duplicateBorder ? 1 : 0); - borderPixels <<= 1; + if (name != null && getRect(name) != null) + throw new GdxRuntimeException("Pixmap has already been packed with name: " + name); + int borderPixels = (padding + (duplicateBorder ? 1 : 0)) << 1; Rectangle rect = new Rectangle(0, 0, image.getWidth() + borderPixels, image.getHeight() + borderPixels); if (rect.getWidth() > pageWidth || rect.getHeight() > pageHeight) { - if (name == null) { - throw new GdxRuntimeException("page size for anonymous pixmap too small"); - } else { - throw new GdxRuntimeException("page size for '" + name + "' too small"); - } + if (name == null) throw new GdxRuntimeException("Page size too small for anonymous pixmap."); + throw new GdxRuntimeException("Page size too small for pixmap: " + name); } - Node node = insert(currPage.root, rect); - + Node node = insert(current.root, rect); if (node == null) { newPage(); - return pack(name, image, directToTexture); + return pack(name, image); } + node.leafName = name == null ? ANONYMOUS : name; - node.leaveName = (name == null) ? ANONYMOUS : name; rect = new Rectangle(node.rect); rect.width -= borderPixels; rect.height -= borderPixels; @@ -245,37 +153,36 @@ private Rectangle pack (String name, Pixmap image, boolean directToTexture) { rect.x += borderPixels; rect.y += borderPixels; if (name != null) { - currPage.rects.put(name, rect); - currPage.addedRects.add(name); - } - if (directToTexture && currPage.texture != null && !currPage.textureNeedsRefresh && !duplicateBorder) { - currPage.texture.bind(); - Gdx.gl.glTexSubImage2D(currPage.texture.glTarget, 0, (int) rect.x, (int) rect.y, (int) rect.width, (int) rect.height, - image.getGLFormat(), image.getGLType(), image.getPixels()); - } else { - currPage.textureNeedsRefresh = true; + current.rects.put(name, rect); + current.addedRects.add(name); } + int rectX = (int)rect.x, rectY = (int)rect.y, rectWidth = (int)rect.width, rectHeight = (int)rect.height; + + if (packToTexture && current.texture != null && !duplicateBorder) { + if (debug) System.out.println("Incrementally updated texture."); + current.texture.bind(); + Gdx.gl.glTexSubImage2D(current.texture.glTarget, 0, rectX, rectY, rectWidth, rectHeight, image.getGLFormat(), + image.getGLType(), image.getPixels()); + } else + current.dirty = true; Blending blending = Pixmap.getBlending(); Pixmap.setBlending(Blending.None); - this.currPage.image.drawPixmap(image, (int)rect.x, (int)rect.y); + + current.image.drawPixmap(image, rectX, rectY); if (duplicateBorder) { - int imageWidth = image.getWidth(); - int imageHeight = image.getHeight(); + int imageWidth = image.getWidth(), imageHeight = image.getHeight(); // Copy corner pixels to fill corners of the padding. - this.currPage.image.drawPixmap(image, 0, 0, 1, 1, (int)rect.x - 1, (int)rect.y - 1, 1, 1); - this.currPage.image.drawPixmap(image, imageWidth - 1, 0, 1, 1, (int)rect.x + (int)rect.width, (int)rect.y - 1, 1, 1); - this.currPage.image.drawPixmap(image, 0, imageHeight - 1, 1, 1, (int)rect.x - 1, (int)rect.y + (int)rect.height, 1, 1); - this.currPage.image.drawPixmap(image, imageWidth - 1, imageHeight - 1, 1, 1, (int)rect.x + (int)rect.width, (int)rect.y - + (int)rect.height, 1, 1); + current.image.drawPixmap(image, 0, 0, 1, 1, rectX - 1, rectY - 1, 1, 1); + current.image.drawPixmap(image, imageWidth - 1, 0, 1, 1, rectX + rectWidth, rectY - 1, 1, 1); + current.image.drawPixmap(image, 0, imageHeight - 1, 1, 1, rectX - 1, rectY + rectHeight, 1, 1); + current.image.drawPixmap(image, imageWidth - 1, imageHeight - 1, 1, 1, rectX + rectWidth, rectY + rectHeight, 1, 1); // Copy edge pixels into padding. - this.currPage.image.drawPixmap(image, 0, 0, imageWidth, 1, (int)rect.x, (int)rect.y - 1, (int)rect.width, 1); - this.currPage.image.drawPixmap(image, 0, imageHeight - 1, imageWidth, 1, (int)rect.x, (int)rect.y + (int)rect.height, - (int)rect.width, 1); - this.currPage.image.drawPixmap(image, 0, 0, 1, imageHeight, (int)rect.x - 1, (int)rect.y, 1, (int)rect.height); - this.currPage.image.drawPixmap(image, imageWidth - 1, 0, 1, imageHeight, (int)rect.x + (int)rect.width, (int)rect.y, 1, - (int)rect.height); + current.image.drawPixmap(image, 0, 0, imageWidth, 1, rectX, rectY - 1, rectWidth, 1); + current.image.drawPixmap(image, 0, imageHeight - 1, imageWidth, 1, rectX, rectY + rectHeight, rectWidth, 1); + current.image.drawPixmap(image, 0, 0, 1, imageHeight, rectX - 1, rectY, 1, rectHeight); + current.image.drawPixmap(image, imageWidth - 1, 0, 1, imageHeight, rectX + rectWidth, rectY, 1, rectHeight); } Pixmap.setBlending(blending); @@ -287,24 +194,19 @@ private void newPage () { Page page = new Page(); page.image = new Pixmap(pageWidth, pageHeight, pageFormat); page.root = new Node(0, 0, pageWidth, pageHeight, null, null, null); - page.rects = new OrderedMap(); + page.rects = new OrderedMap(); pages.add(page); - currPage = page; + current = page; } private Node insert (Node node, Rectangle rect) { - if (node.leaveName == null && node.leftChild != null && node.rightChild != null) { - Node newNode = null; - - newNode = insert(node.leftChild, rect); + if (node.leafName == null && node.leftChild != null && node.rightChild != null) { + Node newNode = insert(node.leftChild, rect); if (newNode == null) newNode = insert(node.rightChild, rect); - return newNode; } else { - if (node.leaveName != null) return null; - + if (node.leafName != null) return null; if (node.rect.width == rect.width && node.rect.height == rect.height) return node; - if (node.rect.width < rect.width || node.rect.height < rect.height) return null; node.leftChild = new Node(); @@ -312,7 +214,6 @@ private Node insert (Node node, Rectangle rect) { int deltaWidth = (int)node.rect.width - (int)rect.width; int deltaHeight = (int)node.rect.height - (int)rect.height; - if (deltaWidth > deltaHeight) { node.leftChild.rect.x = node.rect.x; node.leftChild.rect.y = node.rect.y; @@ -339,7 +240,8 @@ private Node insert (Node node, Rectangle rect) { } } - /** @return the {@link Page} instances created so far. This method is not thread safe! */ + /** @return the {@link Page} instances created so far. If multiple threads are accessing the packer, iterating over the pages + * must be done only after synchronizing on the packer. */ public Array getPages () { return pages; } @@ -375,10 +277,8 @@ public synchronized int getPageIndex (String name) { return -1; } - /** Disposes any Pixmap instances which haven't been used internally to create a texture (e.g. by - * {@link #generateTextureAtlas(TextureFilter, TextureFilter, boolean)} or - * {@link #updateTextureAtlas(TextureAtlas, TextureFilter, TextureFilter, boolean)}). If you call one of those - * methods, you will also need to dispose of the texture atlas. */ + /** Disposes any pixmap pages which don't have a texture. Page pixmaps that have a texture will not be disposed until their + * texture is disposed. */ public synchronized void dispose () { for (Page page : pages) { if (page.texture == null) { @@ -388,85 +288,47 @@ public synchronized void dispose () { disposed = true; } - /** Generates a new {@link TextureAtlas} from the {@link Pixmap} instances inserted so far. After calling this method, - * disposing the PixmapPacker will no longer dispose the atlas' textures or their backing pixmaps. - * @param minFilter - * @param magFilter - * @return the TextureAtlas */ + /** Generates a new {@link TextureAtlas} from the pixmaps inserted so far. After calling this method, disposing the packer will + * no longer dispose the page pixmaps. */ public synchronized TextureAtlas generateTextureAtlas (TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) { TextureAtlas atlas = new TextureAtlas(); updateTextureAtlas(atlas, minFilter, magFilter, useMipMaps); return atlas; } - /** Updates the given {@link TextureAtlas}, adding any new {@link Pixmap} instances packed since the last call to this method. - * This can be used to insert Pixmap instances on a separate thread via {@link #pack(String, Pixmap)} and update the - * TextureAtlas on the rendering thread. This method must be called on the rendering thread. After calling this method, - * disposing the PixmapPacker will no longer dispose the atlas' textures or their backing pixmaps. - */ - public synchronized void updateTextureAtlas(TextureAtlas atlas, TextureFilter minFilter, TextureFilter magFilter, + /** Updates the {@link TextureAtlas}, adding any new {@link Pixmap} instances packed since the last call to this method. This + * can be used to insert Pixmap instances on a separate thread via {@link #pack(String, Pixmap)} and update the TextureAtlas on + * the rendering thread. This method must be called on the rendering thread. After calling this method, disposing the packer + * will no longer dispose the page pixmaps. */ + public synchronized void updateTextureAtlas (TextureAtlas atlas, TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) { + updatePageTextures(minFilter, magFilter, useMipMaps); for (Page page : pages) { - if (page.textureNeedsRefresh) { - if (page.texture == null) { - generatePageTexture(page, minFilter, magFilter, useMipMaps); - } else { - page.texture.load(page.texture.getTextureData()); - } - page.textureNeedsRefresh = false; - } if (page.addedRects.size > 0) { for (String name : page.addedRects) { Rectangle rect = page.rects.get(name); - TextureRegion region = new TextureRegion(page.texture, (int)rect.x, (int)rect.y, (int)rect.width, - (int)rect.height); + TextureRegion region = new TextureRegion(page.texture, (int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height); atlas.addRegion(name, region); } page.addedRects.clear(); + atlas.getTextures().add(page.texture); } - atlas.getTextures().add(page.texture); } } - private void generatePageTexture(Page page, TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) { - page.texture = new Texture(new PixmapTextureData(page.image, page.image.getFormat(), useMipMaps, false, true)) { - @Override - public void dispose() { - super.dispose(); - getTextureData().consumePixmap().dispose(); - } - }; - page.texture.setFilter(minFilter, magFilter); + /** Calls {@link Page#updateTexture(TextureFilter, TextureFilter, boolean) updateTexture} for each page and adds a region to the + * specified array for each page texture. */ + public synchronized void updateTextureRegions (Array regions, TextureFilter minFilter, TextureFilter magFilter, + boolean useMipMaps) { + updatePageTextures(minFilter, magFilter, useMipMaps); + while (regions.size < pages.size) + regions.add(new TextureRegion(pages.get(regions.size).texture)); } - /** - * Update the provided array with texture regions corresponding with the pages of the packer, creating new - * textures as necessary. When you are finished, you must dispose of the regions or a TextureAtlas created by this - * PixmapPacker - disposing this PixmapPacker will no longer dispose these textures or their backing pixmaps. - * - * @param textureRegions The array of TextureRegions to refresh. - * @param minFilter minFilter to use when generating required textures. - * @param magFilter magFilter to use when generating required textures. - * @param useMipMaps useMipMaps to use when generating required textures. - * @param refresh If true, textures will be refreshed if required (i.e. if Pixmaps were added after the - * texture was generated). If false, textures will not be refreshed, and will need to be - * refreshed by a final call to this method with this parameter true or by generating or - * updating a TextureAtlas before the textures can be correctly drawn. - */ - public synchronized void updateTextureRegions(Array textureRegions, TextureFilter minFilter, - TextureFilter magFilter, boolean useMipMaps, boolean refresh) { - if (disposed) return; - for (Page page : pages) { - if (page.texture == null) { - generatePageTexture(page, minFilter, magFilter, useMipMaps); - } else if (refresh && page.textureNeedsRefresh) { - page.texture.load(page.texture.getTextureData()); - page.textureNeedsRefresh = false; - } - if (textureRegions.size < pages.size) { - textureRegions.add(new TextureRegion(page.texture)); - } - } + /** Calls {@link Page#updateTexture(TextureFilter, TextureFilter, boolean) updateTexture} for each page. */ + public synchronized void updatePageTextures (TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) { + for (Page page : pages) + page.updateTexture(minFilter, magFilter, useMipMaps); } public int getPageWidth () { @@ -481,8 +343,85 @@ public int getPadding () { return padding; } - public boolean duplicateBorder () { + public boolean getDuplicateBorder () { return duplicateBorder; } + public boolean getPackToTexture () { + return packToTexture; + } + + /** If true, when a pixmap is packed to a page that has a texture, the portion of the texture where the pixmap was packed is + * updated using glTexSubImage2D. Note if packing many pixmaps, this may be slower than reuploading the whole texture. This + * setting is ignored if {@link #getDuplicateBorder()} is true. */ + public void setPackToTexture (boolean packToTexture) { + this.packToTexture = packToTexture; + } + + static final class Node { + public Node leftChild; + public Node rightChild; + public Rectangle rect; + public String leafName; + + public Node (int x, int y, int width, int height, Node leftChild, Node rightChild, String leafName) { + rect = new Rectangle(x, y, width, height); + this.leftChild = leftChild; + this.rightChild = rightChild; + this.leafName = leafName; + } + + public Node () { + rect = new Rectangle(); + } + } + + /** @author mzechner + * @author Nathan Sweet + * @author Rob Rendell */ + static public class Page { + Node root; + OrderedMap rects; + Pixmap image; + Texture texture; + final Array addedRects = new Array(); + boolean dirty; + + public Pixmap getPixmap () { + return image; + } + + public OrderedMap getRects () { + return rects; + } + + /** Returns the texture for this page, or null if the texture has not been created. + * @see #updateTexture(TextureFilter, TextureFilter, boolean) */ + public Texture getTexture () { + return texture; + } + + /** Creates the texture if it has not been created, else reuploads the entire page pixmap to the texture if the pixmap has + * changed since this method was last called. + * @return true if the texture was created or reuploaded. */ + public boolean updateTexture (TextureFilter minFilter, TextureFilter magFilter, boolean useMipMaps) { + if (texture != null) { + if (!dirty) return false; + if (debug) System.out.println("Reuploaded existing texture."); + texture.load(texture.getTextureData()); + } else { + if (debug) System.out.println("Created new texture."); + texture = new Texture(new PixmapTextureData(image, image.getFormat(), useMipMaps, false, true)) { + @Override + public void dispose () { + super.dispose(); + image.dispose(); + } + }; + texture.setFilter(minFilter, magFilter); + } + dirty = false; + return true; + } + } } diff --git a/gdx/src/com/badlogic/gdx/utils/ObjectSet.java b/gdx/src/com/badlogic/gdx/utils/ObjectSet.java index 28156376190..c72b591df29 100644 --- a/gdx/src/com/badlogic/gdx/utils/ObjectSet.java +++ b/gdx/src/com/badlogic/gdx/utils/ObjectSet.java @@ -359,7 +359,7 @@ public T first () { T[] keyTable = this.keyTable; for (int i = 0, n = capacity + stashSize; i < n; i++) if (keyTable[i] != null) return keyTable[i]; - throw new IllegalStateException("IntSet is empty."); + throw new IllegalStateException("ObjectSet is empty."); } /** Increases the size of the backing array to accommodate the specified number of additional items. Useful before adding many diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/PixmapPackerTest.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/PixmapPackerTest.java index bf377ca99eb..ce1ff08dd89 100755 --- a/tests/gdx-tests/src/com/badlogic/gdx/tests/PixmapPackerTest.java +++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/PixmapPackerTest.java @@ -37,12 +37,12 @@ public class PixmapPackerTest extends GdxTest { OrthographicCamera camera; SpriteBatch batch; - ShapeRenderer shapeRenderer; + ShapeRenderer shapeRenderer; TextureAtlas atlas; int pageToShow = 0; - private Array textureRegions; + Array textureRegions; @Override public void create () { @@ -57,7 +57,7 @@ public void create () { Pixmap pixmap2 = new Pixmap(Gdx.files.internal("data/particle-fire.png")); Pixmap pixmap3 = new Pixmap(Gdx.files.internal("data/isotile.png")); - PixmapPacker packer = new PixmapPacker(1024, 1024, Format.RGBA8888, 2, true); + PixmapPacker packer = new PixmapPacker(1024, 1024, Format.RGBA8888, 2, false); for (int count = 1; count <= 3; ++count) { packer.pack("badlogic " + count, pixmap1); packer.pack("fire " + count, pixmap2); @@ -67,6 +67,8 @@ public void create () { atlas = packer.generateTextureAtlas(TextureFilter.Nearest, TextureFilter.Nearest, false); Gdx.app.log("PixmapPackerTest", "Number of initial textures: " + atlas.getTextures().size); + packer.setPackToTexture(true); + for (int count = 4; count <= 10; ++count) { packer.pack("badlogic " + count, pixmap1); packer.pack("fire " + count, pixmap2); @@ -79,11 +81,11 @@ public void create () { packer.updateTextureAtlas(atlas, TextureFilter.Nearest, TextureFilter.Nearest, false); textureRegions = new Array(); - packer.updateTextureRegions(textureRegions, TextureFilter.Nearest, TextureFilter.Nearest, false, true); + packer.updateTextureRegions(textureRegions, TextureFilter.Nearest, TextureFilter.Nearest, false); Gdx.app.log("PixmapPackerTest", "Number of updated textures: " + atlas.getTextures().size); Gdx.input.setInputProcessor(new InputAdapter() { @Override - public boolean keyDown(int keycode) { + public boolean keyDown (int keycode) { if (keycode >= Input.Keys.NUM_0 && keycode <= Input.Keys.NUM_9) { int number = keycode - Input.Keys.NUM_0; if (number < textureRegions.size) { diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypeAtlasTest.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypeAtlasTest.java index 79532552ed3..eff94be535b 100755 --- a/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypeAtlasTest.java +++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypeAtlasTest.java @@ -69,8 +69,7 @@ static enum FontStyle { OrthographicCamera camera; SpriteBatch batch; String text; - TextureAtlas atlas; - + PixmapPacker packer; FontMap fontMap; public static final int FONT_ATLAS_WIDTH = 1024; @@ -93,7 +92,7 @@ public void create() { long start = System.currentTimeMillis(); int glyphCount = createFonts(); long time = System.currentTimeMillis() - start; - text = glyphCount + " glyphs packed in " + atlas.getTextures().size + " page(s) in " + time + " ms"; + text = glyphCount + " glyphs packed in " + packer.getPages().size + " page(s) in " + time + " ms"; } @@ -128,7 +127,7 @@ public void render() { // draw all glyphs in background batch.setColor(1f, 1f, 1f, 0.15f); - batch.draw(atlas.getTextures().first(), 0, 0); + batch.draw(packer.getPages().first().getTexture(), 0, 0); batch.setColor(1f, 1f, 1f, 1f); batch.end(); } @@ -136,7 +135,7 @@ public void render() { @Override public void dispose() { super.dispose(); - atlas.dispose(); + packer.dispose(); batch.dispose(); } @@ -157,7 +156,7 @@ protected int createFonts() { // ////////////////////////////////////////////////////////////////////////////////////////////////////// // create the pixmap packer - PixmapPacker packer = new PixmapPacker(FONT_ATLAS_WIDTH, FONT_ATLAS_HEIGHT, Format.RGBA8888, 2, false); + packer = new PixmapPacker(FONT_ATLAS_WIDTH, FONT_ATLAS_HEIGHT, Format.RGBA8888, 2, false); fontMap = new FontMap(); int fontCount = 0; @@ -184,11 +183,6 @@ protected int createFonts() { gen.dispose(); } - // Generate a texture atlas, which also finalises the textures. - atlas = packer.generateTextureAtlas(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest, false); - - // We don't need to dispose packer, because its Pixmaps are now owned by the atlas. - // for the demo, show how many glyphs we loaded return fontCount * CHARACTERS.length(); } diff --git a/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypePackTest.java b/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypePackTest.java index 0e016a51ef2..1cb69975152 100755 --- a/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypePackTest.java +++ b/tests/gdx-tests/src/com/badlogic/gdx/tests/extensions/FreeTypePackTest.java @@ -187,7 +187,7 @@ protected int createFonts () { // Get regions from our packer regions = new Array(); - packer.updateTextureRegions(regions, TextureFilter.Nearest, TextureFilter.Nearest, false, true); + packer.updateTextureRegions(regions, TextureFilter.Nearest, TextureFilter.Nearest, false); // No more need for our CPU-based pixmap packer, as our textures are now on GPU packer.dispose();