/** * Copyright 2010 The ForPlay 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. */ package forplay.html; import static com.google.gwt.webgl.client.WebGLRenderingContext.COLOR_ATTACHMENT0; import static com.google.gwt.webgl.client.WebGLRenderingContext.FRAMEBUFFER; import static com.google.gwt.webgl.client.WebGLRenderingContext.RGBA; import static com.google.gwt.webgl.client.WebGLRenderingContext.TEXTURE_2D; import static com.google.gwt.webgl.client.WebGLRenderingContext.UNSIGNED_BYTE; import com.google.gwt.dom.client.CanvasElement; import com.google.gwt.dom.client.ImageElement; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.webgl.client.WebGLFramebuffer; import com.google.gwt.webgl.client.WebGLTexture; import forplay.core.Asserts; import forplay.core.Image; import forplay.core.ResourceCallback; import forplay.core.Transform; class HtmlImage implements Image { private static native boolean isComplete(ImageElement img) /*-{ return img.complete; }-*/; private static native void fakeComplete(CanvasElement img) /*-{ img.complete = true; // CanvasElement doesn't provide a 'complete' property }-*/; ImageElement img; // only used in the WebGL renderer. private WebGLTexture tex, pow2tex; HtmlImage(CanvasElement img) { fakeComplete(img); this.img = img.cast(); } HtmlImage(ImageElement img) { this.img = img; } @Override public int height() { return img == null ? 0 : img.getHeight(); } @Override public void replaceWith(Image image) { Asserts.checkArgument(image instanceof HtmlImage); img = ((HtmlImage) image).img; } @Override public int width() { return img == null ? 0 : img.getWidth(); } @Override public void addCallback(final ResourceCallback<Image> callback) { if (isReady()) { callback.done(this); } else { HtmlPlatform.addEventListener(img, "load", new EventHandler() { @Override public void handleEvent(NativeEvent evt) { callback.done(HtmlImage.this); } }, false); HtmlPlatform.addEventListener(img, "error", new EventHandler() { @Override public void handleEvent(NativeEvent evt) { callback.error(new RuntimeException("Error loading image " + img.getSrc())); } }, false); } } @Override public boolean isReady() { return isComplete(this.img); } /* * Clears textures associated with this image. This does not destroy the image -- a subsequent * call to ensureTexture() will recreate them. */ void clearTexture(HtmlGraphicsGL gfx) { if (pow2tex == tex) { pow2tex = null; } if (tex != null) { gfx.destroyTexture(tex); tex = null; } if (pow2tex != null) { gfx.destroyTexture(pow2tex); pow2tex = null; } } WebGLTexture ensureTexture(HtmlGraphicsGL gfx, boolean repeatX, boolean repeatY) { // Create requested textures if loaded. if (isReady()) { if (repeatX || repeatY) { scaleTexture(gfx, repeatX, repeatY); return pow2tex; } else { loadTexture(gfx); return tex; } } return null; } private void loadTexture(HtmlGraphicsGL gfx) { if (tex != null) { return; } tex = gfx.createTexture(false, false); gfx.updateTexture(tex, img); } private void scaleTexture(HtmlGraphicsGL gfx, boolean repeatX, boolean repeatY) { if (pow2tex != null) { return; } // Ensure that 'tex' is loaded. We use it below. loadTexture(gfx); // GL requires pow2 on axes that repeat. int width = nextPowerOfTwo(width()), height = nextPowerOfTwo(height()); // Don't scale if it's already a power of two. if ((width == 0) && (height == 0)) { pow2tex = tex; return; } // width/height == 0 => already a power of two. if (width == 0) { width = width(); } if (height == 0) { height = height(); } // Create the pow2 texture. pow2tex = gfx.createTexture(repeatX, repeatY); gfx.gl.bindTexture(TEXTURE_2D, pow2tex); gfx.gl.texImage2D(TEXTURE_2D, 0, RGBA, width, height, 0, RGBA, UNSIGNED_BYTE, null); // Point a new framebuffer at it. WebGLFramebuffer fbuf = gfx.gl.createFramebuffer(); gfx.bindFramebuffer(fbuf, width, height); gfx.gl.framebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, pow2tex, 0); // Render the scaled texture into the framebuffer. // (rebind the texture because gfx.bindFramebuffer() may have bound it when flushing) gfx.gl.bindTexture(TEXTURE_2D, pow2tex); gfx.drawTexture(tex, width(), height(), Transform.IDENTITY, 0, height, width, -height, false, false, 1); gfx.flush(); gfx.bindFramebuffer(); gfx.gl.deleteFramebuffer(fbuf); } /** * Returns the next largest power of two, or zero if x is already a power of two. * * TODO(jgw): Is there no better way to do this than all this bit twiddling? */ private int nextPowerOfTwo(int x) { Asserts.checkArgument(x < 0x10000); int bit = 0x8000, highest = -1, count = 0; for (int i = 15; i >= 0; --i, bit >>= 1) { if ((x & bit) != 0) { ++count; if (highest == -1) { highest = i; } } } if (count <= 1) { return 0; } return 1 << (highest + 1); } }