/** * Copyright 2008 - 2015 The Loon Game Engine Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * @project loon * @author cping * @email:javachenpeng@yahoo.com * @version 0.5 */ package loon; import java.util.Arrays; import loon.LTextureBatch.Cache; import loon.canvas.Canvas; import loon.canvas.Image; import loon.canvas.LColor; import loon.event.Updateable; import loon.geom.Affine2f; import loon.opengl.BaseBatch; import loon.opengl.GL20; import loon.opengl.GLPaint; import loon.opengl.Painter; import loon.utils.CollectionUtils; import loon.utils.GLUtils; import loon.utils.IntMap; import loon.utils.NumberUtils; import loon.utils.Scale; import loon.utils.StringUtils; import loon.utils.processes.RealtimeProcess; import loon.utils.processes.RealtimeProcessManager; import loon.utils.reply.UnitPort; import loon.utils.timer.LTimerContext; import static loon.opengl.GL20.*; public class LTexture extends Painter implements LRelease { public static LTexture createTexture(int w, int h, Format config) { return LTextures.createTexture(w, h, config); } public static LTexture createTexture(int w, int h) { return LTextures.createTexture(w, h, Format.DEFAULT); } public static LTexture createTexture(final String path) { return LTextures.loadTexture(path); } private boolean _disabledTexture = false; private boolean _drawing = false; private boolean _copySize = false; private boolean _scaleSize = false; private int[] _cachePixels; private String source; private Image _image; private int imageWidth = 1, imageHeight = 1; public float xOff = 0.0f; public float yOff = 0.0f; public float widthRatio = 1.0f; public float heightRatio = 1.0f; private LColor[] colors; private LTextureBatch batch; private boolean isBatch; String tmpLazy = "tex" + System.currentTimeMillis(); int refCount; public final static class Format { public static Format NEAREST = new Format(true, false, false, GL_NEAREST, GL_NEAREST, false); public static Format LINEAR = new Format(true, false, false, GL_LINEAR, GL_LINEAR, false); public static Format UNMANAGED = new Format(false, false, false, GL_NEAREST, GL_LINEAR, false); public static Format DEFAULT = LINEAR; public final boolean managed; public final boolean repeatX, repeatY; public final int minFilter, magFilter; public final boolean mipmaps; public Format(boolean managed, boolean repeatX, boolean repeatY, int minFilter, int magFilter, boolean mipmaps) { this.managed = managed; this.repeatX = repeatX; this.repeatY = repeatY; this.minFilter = minFilter; this.magFilter = magFilter; this.mipmaps = mipmaps; } public Format repeat(boolean repeatX, boolean repeatY) { return new Format(managed, repeatX, repeatY, minFilter, magFilter, mipmaps); } public int toTexWidth(int sourceWidth) { return (repeatX || mipmaps) ? GLUtils.nextPOT(sourceWidth) : sourceWidth; } public int toTexHeight(int sourceHeight) { return (repeatY || mipmaps) ? GLUtils.nextPOT(sourceHeight) : sourceHeight; } @Override public String toString() { String repstr = (repeatX ? "x" : "") + (repeatY ? "y" : ""); return "[managed=" + managed + ", repeat=" + repstr + ", filter=" + minFilter + "/" + magFilter + ", mipmaps=" + mipmaps + "]"; } } private int id; private Format config; public int getID() { return id; } public Format getFormat() { return config; } private int pixelWidth; private int pixelHeight; private Scale scale; private float displayWidth; private float displayHeight; private Graphics gfx; // _closed是删除标记,disposed是已经真的被删掉 boolean _closed, _disposed; IntMap<LTexture> childs; LTexture parent; public boolean isChild() { return parent != null; } public LTexture getParent() { return parent; } public int pixelWidth() { return pixelWidth; } public int pixelHeight() { return pixelHeight; } static int _countTexture = 0; LTexture() { this._isLoaded = false; } public LTexture(Graphics gfx, int id, Format config, int pixWidth, int pixHeight, Scale scale, float dispWidth, float dispHeight) { this.gfx = gfx; this.id = id; this.config = config; this.pixelWidth = pixWidth; this.pixelHeight = pixHeight; this.scale = scale; this.displayWidth = dispWidth; this.displayHeight = dispHeight; this._isLoaded = false; LTextures.putTexture(this); _countTexture++; } public float toTexWidth() { return config.toTexWidth(pixelWidth); } public float toTexHeight() { return config.toTexHeight(pixelHeight); } public void reference() { if (config.managed) { refCount++; } } public void release() { if (config.managed) { if (--refCount == 0) { close(true); } } } public String src() { return getSource(); } public String getSource() { return source; } public Image getImage() { if ((_image == null || _image.isClosed()) && !StringUtils.isEmpty(source)) { _image = BaseIO.loadImage(source); } if (_image == null && _cachePixels != null) { _image = Image.createImage(imageWidth, imageHeight); _image.setPixels(_cachePixels, imageWidth, imageHeight); } int w = getWidth(); int h = getHeight(); if (w != displayWidth || h != displayHeight) { if (_image != null) { Image tmp = _image.getSubImage((int) (this.xOff * this.displayWidth), (int) (this.yOff * this.displayHeight), w, h); return tmp; } } return _image; } public void reload() { if (_closed || _disposed) { return; } if (parent != null) { parent.reload(); return; } this._isLoaded = false; this.loadTexture(); } public void loadTexture() { if (parent != null) { parent.loadTexture(); return; } if (childs != null) { for (LTexture tex : childs.values()) { tex._isLoaded = _isLoaded; } } if (_image != null && !_isLoaded) { update(_image); } else if (!_isLoaded) { if (!StringUtils.isEmpty(source) && (source.indexOf('<') == -1 && source.indexOf('>') == -1)) { _image = BaseIO.loadImage(source); } else if (_cachePixels != null) { _image = Image.createCanvas(imageWidth, imageHeight).image; _image.setPixels(_cachePixels, imageWidth, imageHeight); } if (_image != null) { update(_image); } } } private int _memorySize = 0; public void update(final Image image) { update(image, true); } public void update(final Image image, final boolean closed) { if (image == null) { throw LSystem.runThrow("the image is null, can not conversion it into texture ."); } if (parent != null) { parent.update(image, closed); return; } if (_drawing) { return; } this._drawing = true; this.source = image.getSource(); if (image != null) { if (config.repeatX || config.repeatY || config.mipmaps) { int pixWidth = image.pixelWidth(), pixHeight = image.pixelHeight(); int potWidth = config.toTexWidth(pixWidth), potHeight = config.toTexWidth(pixHeight); if (potWidth != pixWidth || potHeight != pixHeight) { Canvas scaled = gfx.createCanvasImpl(Scale.ONE, potWidth, potHeight); scaled.draw(image, 0, 0, potWidth, potHeight); scaled.image.upload(gfx, LTexture.this); scaled.close(); } else { image.upload(gfx, LTexture.this); } } else { image.upload(gfx, LTexture.this); } imageWidth = image.getWidth(); imageHeight = image.getHeight(); if (config.mipmaps) { gfx.gl.glGenerateMipmap(GL_TEXTURE_2D); } } if (config.mipmaps) { _memorySize = imageWidth * imageHeight * 4 * (1 + 1 / 3); } else { _memorySize = imageWidth * imageHeight * 4; } LTextureBatch.isBatchCacheDitry = true; _isLoaded = true; if (closed) { if (image != null && (image.getSource() == null || image.getSource().indexOf("<canvas>") != -1) && LSystem.base() != null && LSystem.base().setting.saveTexturePixels) { _cachePixels = CollectionUtils.copyOf(image.getPixels()); } if (image != null && image.toClose()) { image.destroy(); } } _image = image; _drawing = false; } public void bind() { GLUtils.bindTexture(LSystem.base().graphics().gl, id); } public void bind(int unit) { LSystem.base().graphics().gl.glActiveTexture(GL20.GL_TEXTURE0 + unit); GLUtils.bindTexture(LSystem.base().graphics().gl, id); } public boolean isClose() { if (parent != null) { return parent.isClose(); } return _disposed || _closed; } public boolean disposed() { if (parent != null) { return parent.disposed(); } return _disposed && _closed; } public UnitPort disposeAct() { return new UnitPort() { public void onEmit() { close(); } }; } @Override public LTexture texture() { return this; } @Override public float width() { float result = displayWidth * widthRatio - displayWidth * xOff; return result > 0 ? result : -result; } @Override public float height() { float result = displayHeight * heightRatio - displayHeight * yOff; return result > 0 ? result : -result; } @Override public float sx() { return xOff; } @Override public float sy() { return yOff; } @Override public float tx() { return widthRatio; } @Override public float ty() { return heightRatio; } @Override public void addToBatch(BaseBatch batch, int tint, Affine2f tx, float x, float y, float width, float height) { if (isClose()) { return; } batch.addQuad(this, tint, tx, x, y, width, height); } @Override public void addToBatch(BaseBatch batch, int tint, Affine2f tx, float dx, float dy, float dw, float dh, float sx, float sy, float sw, float sh) { if (isClose()) { return; } batch.addQuad(this, tint, tx, dx, dy, dw, dh, sx, sy, sw, sh); } public void closeChildAll() { if (childs != null) { for (LTexture tex2d : childs.values()) { if (tex2d != null) { tex2d.close(); tex2d = null; } } } } public boolean isChildAllClose() { if (childs != null) { for (LTexture tex2d : childs.values()) { if (tex2d != null && !tex2d.isClose()) { return false; } } } return true; } @Override public String toString() { return "Texture[id=" + id + ", psize=" + pixelWidth + "x" + pixelHeight + ", dsize=" + displayWidth + "x" + displayHeight + " @ " + scale + ", config=" + config + "]"; } public float getDisplayWidth() { return displayWidth; } public float getDisplayHeight() { return displayHeight; } @Override protected void finalize() { if (!_disposed && !_closed) { gfx.queueForDispose(this); } } public LTexture cpy() { return copy(); } public LTexture cpy(final float x, final float y, final float width, final float height) { return copy(x, y, width, height); } public LTexture copy() { return copy(0, 0, width(), height()); } public LTexture copy(final float x, final float y, final float width, final float height) { int hashCode = 1; hashCode = LSystem.unite(hashCode, x); hashCode = LSystem.unite(hashCode, y); hashCode = LSystem.unite(hashCode, width); hashCode = LSystem.unite(hashCode, height); if (childs == null) { childs = new IntMap<LTexture>(10); } synchronized (childs) { LTexture cache = childs.get(hashCode); if (cache != null) { return cache; } final LTexture copy = new LTexture(); refCount++; copy.parent = LTexture.this; copy.id = id; copy._isLoaded = _isLoaded; copy.gfx = gfx; copy.config = config; copy.source = source; copy.scale = scale; copy.imageWidth = imageWidth; copy.imageHeight = imageHeight; copy._image = _image; copy._cachePixels = _cachePixels; copy._copySize = true; copy.pixelWidth = (int) (this.pixelWidth * this.widthRatio); copy.pixelHeight = (int) (this.pixelHeight * this.heightRatio); copy.displayWidth = this.displayWidth * this.widthRatio; copy.displayHeight = this.displayHeight * this.heightRatio; copy.xOff = (((float) x / copy.displayWidth) * this.widthRatio) + this.xOff; copy.yOff = (((float) y / copy.displayHeight) * this.heightRatio) + this.yOff; copy.widthRatio = (((float) width / copy.displayWidth) * widthRatio) + copy.xOff; copy.heightRatio = (((float) height / copy.displayHeight) * heightRatio) + copy.yOff; copy._disabledTexture = _disabledTexture; childs.put(hashCode, copy); return copy; } } public boolean isCopy() { return _copySize; } public boolean isScale() { return _scaleSize; } public LTexture scale(final float width, final float height) { int hashCode = 1; hashCode = LSystem.unite(hashCode, width); hashCode = LSystem.unite(hashCode, height); if (childs == null) { childs = new IntMap<LTexture>(10); } synchronized (childs) { LTexture cache = childs.get(hashCode); if (cache != null) { return cache; } final LTexture copy = new LTexture(); refCount++; copy.parent = LTexture.this; copy.id = id; copy._isLoaded = _isLoaded; copy.gfx = gfx; copy.config = config; copy.source = source; copy.scale = scale; copy.imageWidth = imageWidth; copy.imageHeight = imageHeight; copy._image = _image; copy._cachePixels = _cachePixels; copy._copySize = true; copy._scaleSize = true; copy.pixelWidth = (int) (this.pixelWidth * this.widthRatio); copy.pixelHeight = (int) (this.pixelHeight * this.heightRatio); copy.displayWidth = this.displayWidth * this.widthRatio; copy.displayHeight = this.displayHeight * this.heightRatio; copy.xOff = this.xOff; copy.yOff = this.yOff; copy.widthRatio = (((float) width / copy.displayWidth) * widthRatio) + copy.xOff; copy.heightRatio = (((float) height / copy.displayHeight) * heightRatio) + copy.yOff; copy._disabledTexture = _disabledTexture; childs.put(hashCode, copy); return copy; } } public void setImageColor(float r, float g, float b, float a) { setColor(TOP_LEFT, r, g, b, a); setColor(TOP_RIGHT, r, g, b, a); setColor(BOTTOM_LEFT, r, g, b, a); setColor(BOTTOM_RIGHT, r, g, b, a); } public void setImageColor(float r, float g, float b) { setColor(TOP_LEFT, r, g, b); setColor(TOP_RIGHT, r, g, b); setColor(BOTTOM_LEFT, r, g, b); setColor(BOTTOM_RIGHT, r, g, b); } public void setImageColor(LColor c) { if (c == null) { return; } setImageColor(c.r, c.g, c.b, c.a); } public void setColor(GLPaint paint) { if (colors == null) { colors = new LColor[4]; } colors[0] = paint.getTopLeftColor(); colors[1] = paint.getTopRightColor(); colors[2] = paint.getBottomLeftColor(); colors[3] = paint.getBottomRightColor(); } public void setColor(int corner, float r, float g, float b, float a) { if (colors == null) { colors = new LColor[] { new LColor(1f, 1f, 1f, 1f), new LColor(1f, 1f, 1f, 1f), new LColor(1f, 1f, 1f, 1f), new LColor(1f, 1f, 1f, 1f) }; } colors[corner].r = r; colors[corner].g = g; colors[corner].b = b; colors[corner].a = a; } public void setColor(int corner, float r, float g, float b) { if (colors == null) { colors = new LColor[] { new LColor(1f, 1f, 1f, 1f), new LColor(1f, 1f, 1f, 1f), new LColor(1f, 1f, 1f, 1f), new LColor(1f, 1f, 1f, 1f) }; } colors[corner].r = r; colors[corner].g = g; colors[corner].b = b; } public LTextureBatch getTextureBatch() { makeBatch(null); return batch; } public LTextureBatch getTextureBatch(String name) { makeBatch(name); return batch; } void makeBatch(String name) { if (!isBatch) { batch = new LTextureBatch(this); if (!StringUtils.isEmpty(name)) { batch.setTextureBatchName(name); } isBatch = true; } } void freeBatch() { if (isBatch) { if (batch != null) { batch.close(); batch = null; isBatch = false; } } } public void postCache(Cache cache) { if (isBatch()) { batch.postCache(cache, colors == null ? null : colors[0], 0, 0); } } public boolean isBatch() { return (isBatch && batch.isLoaded); } public void glBegin() { makeBatch(null); batch.begin(); } public void glEnd() { if (isBatch) { batch.end(); } } public void setBatchPos(float x, float y) { if (isBatch) { batch.setLocation(x, y); } } public boolean isBatchLocked() { return isBatch && batch.isCacheLocked; } public void glCacheCommit() { if (isBatch) { batch.postLastCache(); } } public void draw(float x, float y) { draw(x, y, width(), height()); } public void draw(float x, float y, float width, float height) { if (isBatch) { batch.draw(colors, x, y, width, height); } else { gfx.game.display().GL().draw(this, x, y, width, height, colors == null ? null : colors[0]); } } public void draw(float x, float y, LColor[] c) { if (isBatch) { batch.draw(c, x, y, width(), height()); } else { gfx.game.display().GL().draw(this, x, y, width(), height(), c == null ? null : c[0]); } } public void draw(float x, float y, LColor c) { if (isBatch) { LColor old = (colors == null ? LColor.white : colors[0]); final boolean update = checkUpdateColor(c); if (update) { setImageColor(c); } batch.draw(colors, x, y, width(), height()); if (update) { setImageColor(old); } } else { gfx.game.display().GL().draw(this, x, y, width(), height(), c); } } public void draw(float x, float y, float width, float height, LColor c) { if (isBatch) { LColor old = (colors == null ? LColor.white : colors[0]); final boolean update = checkUpdateColor(c); if (update) { setImageColor(c); } batch.draw(colors, x, y, width, height); if (update) { setImageColor(old); } } else { gfx.game.display().GL().draw(this, x, y, width, height, c); } } public void drawFlipX(float x, float y, LColor c) { if (isBatch) { LColor old = (colors == null ? LColor.white : colors[0]); final boolean update = checkUpdateColor(c); if (update) { setImageColor(c); } batch.draw(colors, x, y, width(), height(), 0, 0, width(), height(), true, false); if (update) { setImageColor(old); } } else { gfx.game.display().GL().drawFlip(this, x, y, c); } } public void drawFlipY(float x, float y, LColor c) { if (isBatch) { LColor old = (colors == null ? LColor.white : colors[0]); final boolean update = checkUpdateColor(c); if (update) { setImageColor(c); } batch.draw(colors, x, y, width(), height(), 0, 0, width(), height(), false, true); if (update) { setImageColor(old); } } else { gfx.game.display().GL().drawMirror(this, x, y, c); } } public void draw(float x, float y, float width, float height, float x1, float y1, float x2, float y2, LColor[] c) { if (isBatch) { batch.draw(c, x, y, width, height, x1, y1, x2, y2); } else { gfx.game.display().GL().draw(this, x, y, width, height, x1, y1, x2, y2, c == null ? null : c[0]); } } public void drawEmbedded(float x, float y, float width, float height, float x1, float y1, float x2, float y2, LColor c) { draw(x, y, width - x, height - y, x1, y1, x2, y2, c); } public void draw(float x, float y, float width, float height, float x1, float y1, float x2, float y2, LColor c) { if (isBatch) { LColor old = (colors == null ? LColor.white : colors[0]); final boolean update = checkUpdateColor(c); if (update) { setImageColor(c); } batch.draw(colors, x, y, width, height, x1, y1, x2, y2); if (update) { setImageColor(old); } } else { gfx.game.display().GL().draw(this, x, y, width, height, x1, y1, x2, y2, c); } } public void draw(float x, float y, float srcX, float srcY, float srcWidth, float srcHeight) { if (isBatch) { batch.draw(colors, x, y, srcWidth - srcX, srcHeight - srcY, srcX, srcY, srcWidth, srcHeight); } else { gfx.game.display().GL().draw(this, x, y, srcWidth - srcX, srcHeight - srcY, srcX, srcY, srcWidth, srcHeight, colors == null ? null : colors[0]); } } public void drawEmbedded(float x, float y, float width, float height, float x1, float y1, float x2, float y2) { draw(x, y, width - x, height - y, x1, y1, x2, y2); } public void draw(float x, float y, float width, float height, float x1, float y1, float x2, float y2) { if (isBatch) { batch.draw(colors, x, y, width, height, x1, y1, x2, y2); } else { gfx.game.display().GL().draw(this, x, y, width, height, x1, y1, x2, y2, colors == null ? null : colors[0]); } } public void draw(float x, float y, float rotation) { draw(x, y, this.width(), this.height(), 0, 0, this.width(), this.height(), rotation, colors == null ? null : colors[0]); } public void draw(float x, float y, float w, float h, float rotation, LColor c) { draw(x, y, w, h, 0, 0, this.width(), this.height(), rotation, c); } public void draw(float x, float y, float width, float height, float x1, float y1, float x2, float y2, float rotation, LColor c) { if (rotation == 0) { draw(x, y, width, height, x1, y1, x2, y2, c); return; } if (isBatch) { LColor old = (colors == null ? LColor.white : colors[0]); final boolean update = checkUpdateColor(c); if (update) { setImageColor(c); } batch.draw(colors, x, y, width, height, x1, y1, x2, y2, rotation); if (update) { setImageColor(old); } } else { gfx.game.display().GL().draw(this, x, y, width, height, x1, y1, x2, y2, c, rotation); } } private boolean checkUpdateColor(LColor c) { if (c == null) { setColor(TOP_LEFT, 1f, 1f, 1f, 1f); setColor(TOP_RIGHT, 1f, 1f, 1f, 1f); setColor(BOTTOM_LEFT, 1f, 1f, 1f, 1f); setColor(BOTTOM_RIGHT, 1f, 1f, 1f, 1f); } return c != null && !LColor.white.equals(c); } public Cache saveBatchCache() { return newBatchCache(); } public Cache newBatchCache() { if (isBatch) { return batch.newCache(); } return null; } public void postLastBatchCache() { if (isBatch) { batch.postLastCache(); } } public void disposeLastCache() { if (isBatch) { batch.disposeLastCache(); } } @Override public boolean equals(Object o) { if (o == null) { return false; } if (o == this) { return true; } if (o instanceof LTexture) { LTexture tmp = (LTexture) o; if (this == tmp) { return true; } if (source != null && !source.equals(tmp.source)) { return false; } if ((tmp.width() != width()) || (tmp.height() != height())) { return false; } if (this.id == tmp.id && this.xOff == tmp.xOff && this.yOff == tmp.yOff && this.widthRatio == tmp.widthRatio && this.heightRatio == tmp.heightRatio && this.config == tmp.config && this.parent == tmp.parent && this.displayWidth == tmp.displayWidth && this.displayHeight == tmp.displayHeight && this.pixelWidth == tmp.pixelWidth && this.pixelHeight == tmp.pixelHeight) { if (_image != null && tmp._image != null) { return Arrays.equals(_image.getPixels(), tmp._image.getPixels()); } return true; } } return false; } void free() { if (disposed()) { return; } if (_disabledTexture) { return; } if (!_isLoaded) { return; } if (parent != null) { parent.free(); return; } final int textureId = id; if (!LTextures.contains(textureId)) { return; } LTextures.removeTexture(this); final Updateable update = new Updateable() { @Override public void action(Object a) { if (parent == null) { if (LTextures.delTexture(textureId)) { synchronized (LTexture.class) { if (LSystem._base.setting.disposeTexture && !_disposed && _closed) { GLUtils.deleteTexture(gfx.gl, textureId); _disposed = true; } if (_image != null) { _image.close(); _image = null; } if (childs != null) { childs.clear(); childs = null; } _cachePixels = null; _isLoaded = false; _closed = true; _memorySize = 0; if (batch != null) { batch.close(); batch = null; } isBatch = false; } } } } }; if (LTextureBatch.isRunningCache() && source.indexOf("<canvas>") == -1) { RealtimeProcess process = new RealtimeProcess() { @Override public void run(LTimerContext time) { if (!LTextureBatch.isRunningCache()) { LSystem.load(update); kill(); } } }; process.setDelay(LSystem.SECOND); RealtimeProcessManager.get().addProcess(process); } else { LSystem.load(update); } } public int getWidth() { return (int) width(); } public int getHeight() { return (int) height(); } @Override public int hashCode() { int result = getID(); result = LSystem.unite(result, width() != +0.0f ? NumberUtils.floatToIntBits(width()) : 0); result = LSystem.unite(result, height() != +0.0f ? NumberUtils.floatToIntBits(height()) : 0); result = LSystem.unite(result, disposed() ? 1 : 0); result = LSystem.unite(result, xOff); result = LSystem.unite(result, yOff); result = LSystem.unite(result, widthRatio); result = LSystem.unite(result, heightRatio); result = LSystem.unite(result, childs == null ? 0 : childs.size); return result; } public float getMinU() { return xOff; } public float getMinV() { return yOff; } public float getMaxU() { return widthRatio; } public float getMaxV() { return heightRatio; } public int getMemSize() { return _memorySize; } @Override public void close() { close(false); } public LTexture setDisabledTexture(boolean d) { _disabledTexture = d; return this; } public boolean isDisabledTexture() { return _disabledTexture; } public void close(boolean forcedDelete) { if (_disabledTexture) { return; } if (isClose()) { return; } if (forcedDelete) { if (childs != null) { childs.clear(); } } else if (!isChildAllClose()) { return; } int size = LTextures.removeTextureRef(this, true); if (forcedDelete) { refCount = 0; if (parent != null) { parent.close(); } else { _closed = true; _countTexture--; free(); } } else if (size <= 0) { if (parent != null) { parent.close(); } else { _closed = true; _countTexture--; free(); } } } }