/* * Copyright (C) 2010 The Android Open Source Project * * 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 com.android.gallery3d.ui; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; import junit.framework.TestCase; import java.util.Arrays; import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.opengles.GL11; @SmallTest public class GLCanvasTest extends TestCase { private static final String TAG = "GLCanvasTest"; private static GLPaint newColorPaint(int color) { GLPaint paint = new GLPaint(); paint.setColor(color); return paint; } @SmallTest public void testSetSize() { GL11 glStub = new GLStub(); GLCanvas canvas = new GLCanvasImpl(glStub); canvas.setSize(100, 200); canvas.setSize(1000, 100); try { canvas.setSize(-1, 100); fail(); } catch (Throwable ex) { // expected. } } @SmallTest public void testClearBuffer() { new ClearBufferTest().run(); } private static class ClearBufferTest extends GLMock { void run() { GLCanvas canvas = new GLCanvasImpl(this); assertEquals(0, mGLClearCalled); canvas.clearBuffer(); assertEquals(GL10.GL_COLOR_BUFFER_BIT, mGLClearMask); assertEquals(1, mGLClearCalled); } } @SmallTest public void testAnimationTime() { GL11 glStub = new GLStub(); GLCanvas canvas = new GLCanvasImpl(glStub); long[] testData = {0, 1, 2, 1000, 10000, Long.MAX_VALUE}; for (long v : testData) { canvas.setCurrentAnimationTimeMillis(v); assertEquals(v, canvas.currentAnimationTimeMillis()); } try { canvas.setCurrentAnimationTimeMillis(-1); fail(); } catch (Throwable ex) { // expected. } } @SmallTest public void testSetColor() { new SetColorTest().run(); } // This test assumes we use pre-multipled alpha blending and should // set the blending function and color correctly. private static class SetColorTest extends GLMock { void run() { int[] testColors = new int[] { 0, 0xFFFFFFFF, 0xFF000000, 0x00FFFFFF, 0x80FF8001, 0x7F010101, 0xFEFEFDFC, 0x017F8081, 0x027F8081, 0x2ADE4C4D }; GLCanvas canvas = new GLCanvasImpl(this); canvas.setSize(400, 300); // Test one color to make sure blend function is set. assertEquals(0, mGLColorCalled); canvas.drawLine(0, 0, 1, 1, newColorPaint(0x7F804020)); assertEquals(1, mGLColorCalled); assertEquals(0x7F402010, mGLColor); assertPremultipliedBlending(this); // Test other colors to make sure premultiplication is right for (int c : testColors) { float a = (c >>> 24) / 255f; float r = ((c >> 16) & 0xff) / 255f; float g = ((c >> 8) & 0xff) / 255f; float b = (c & 0xff) / 255f; int pre = makeColor4f(a * r, a * g, a * b, a); mGLColorCalled = 0; canvas.drawLine(0, 0, 1, 1, newColorPaint(c)); assertEquals(1, mGLColorCalled); assertEquals(pre, mGLColor); } } } @SmallTest public void testSetGetMultiplyAlpha() { GL11 glStub = new GLStub(); GLCanvas canvas = new GLCanvasImpl(glStub); canvas.setAlpha(1f); assertEquals(1f, canvas.getAlpha()); canvas.setAlpha(0f); assertEquals(0f, canvas.getAlpha()); canvas.setAlpha(0.5f); assertEquals(0.5f, canvas.getAlpha()); canvas.multiplyAlpha(0.5f); assertEquals(0.25f, canvas.getAlpha()); canvas.multiplyAlpha(0f); assertEquals(0f, canvas.getAlpha()); try { canvas.setAlpha(-0.01f); fail(); } catch (Throwable ex) { // expected. } try { canvas.setAlpha(1.01f); fail(); } catch (Throwable ex) { // expected. } } @SmallTest public void testAlpha() { new AlphaTest().run(); } private static class AlphaTest extends GLMock { void run() { GLCanvas canvas = new GLCanvasImpl(this); canvas.setSize(400, 300); assertEquals(0, mGLColorCalled); canvas.setAlpha(0.48f); canvas.drawLine(0, 0, 1, 1, newColorPaint(0xFF804020)); assertPremultipliedBlending(this); assertEquals(1, mGLColorCalled); assertEquals(0x7A3D1F0F, mGLColor); } } @SmallTest public void testDrawLine() { new DrawLineTest().run(); } // This test assumes the drawLine() function use glDrawArrays() with // GL_LINE_STRIP mode to draw the line and the input coordinates are used // directly. private static class DrawLineTest extends GLMock { private int mDrawArrayCalled = 0; private final int[] mResult = new int[4]; @Override public void glDrawArrays(int mode, int first, int count) { assertNotNull(mGLVertexPointer); assertEquals(GL10.GL_LINE_STRIP, mode); assertEquals(2, count); mGLVertexPointer.bindByteBuffer(); double[] coord = new double[4]; mGLVertexPointer.getArrayElement(first, coord); mResult[0] = (int) coord[0]; mResult[1] = (int) coord[1]; mGLVertexPointer.getArrayElement(first + 1, coord); mResult[2] = (int) coord[0]; mResult[3] = (int) coord[1]; mDrawArrayCalled++; } void run() { GLCanvas canvas = new GLCanvasImpl(this); canvas.setSize(400, 300); canvas.drawLine(2, 7, 1, 8, newColorPaint(0) /* color */); assertTrue(mGLVertexArrayEnabled); assertEquals(1, mDrawArrayCalled); Log.v(TAG, "result = " + Arrays.toString(mResult)); int[] answer = new int[] {2, 7, 1, 8}; for (int i = 0; i < answer.length; i++) { assertEquals(answer[i], mResult[i]); } } } @SmallTest public void testFillRect() { new FillRectTest().run(); } // This test assumes the drawLine() function use glDrawArrays() with // GL_TRIANGLE_STRIP mode to draw the line and the input coordinates // are used directly. private static class FillRectTest extends GLMock { private int mDrawArrayCalled = 0; private final int[] mResult = new int[8]; @Override public void glDrawArrays(int mode, int first, int count) { assertNotNull(mGLVertexPointer); assertEquals(GL10.GL_TRIANGLE_STRIP, mode); assertEquals(4, count); mGLVertexPointer.bindByteBuffer(); double[] coord = new double[4]; for (int i = 0; i < 4; i++) { mGLVertexPointer.getArrayElement(first + i, coord); mResult[i * 2 + 0] = (int) coord[0]; mResult[i * 2 + 1] = (int) coord[1]; } mDrawArrayCalled++; } void run() { GLCanvas canvas = new GLCanvasImpl(this); canvas.setSize(400, 300); canvas.fillRect(2, 7, 1, 8, 0 /* color */); assertTrue(mGLVertexArrayEnabled); assertEquals(1, mDrawArrayCalled); Log.v(TAG, "result = " + Arrays.toString(mResult)); // These are the four vertics that should be used. int[] answer = new int[] { 2, 7, 3, 7, 3, 15, 2, 15}; int count[] = new int[4]; // Count the number of appearances for each vertex. for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (answer[i * 2] == mResult[j * 2] && answer[i * 2 + 1] == mResult[j * 2 + 1]) { count[i]++; } } } // Each vertex should appear exactly once. for (int i = 0; i < 4; i++) { assertEquals(1, count[i]); } } } @SmallTest public void testTransform() { new TransformTest().run(); } // This test assumes glLoadMatrixf is used to load the model view matrix, // and glOrthof is used to load the projection matrix. // // The projection matrix is set to an orthogonal projection which is the // inverse of viewport transform. So the model view matrix maps input // directly to screen coordinates (default no scaling, and the y-axis is // reversed). // // The matrix here are all listed in column major order. // private static class TransformTest extends GLMock { private final float[] mModelViewMatrixUsed = new float[16]; private final float[] mProjectionMatrixUsed = new float[16]; @Override public void glDrawArrays(int mode, int first, int count) { copy(mModelViewMatrixUsed, mGLModelViewMatrix); copy(mProjectionMatrixUsed, mGLProjectionMatrix); } private void copy(float[] dest, float[] src) { System.arraycopy(src, 0, dest, 0, 16); } void run() { GLCanvas canvas = new GLCanvasImpl(this); canvas.setSize(40, 50); int color = 0; // Initial matrix canvas.drawLine(0, 0, 1, 1, newColorPaint(color)); assertMatrixEq(new float[] { 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 50, 0, 1 }, mModelViewMatrixUsed); assertMatrixEq(new float[] { 2f / 40, 0, 0, 0, 0, 2f / 50, 0, 0, 0, 0, -1, 0, -1, -1, 0, 1 }, mProjectionMatrixUsed); // Translation canvas.translate(3, 4, 5); canvas.drawLine(0, 0, 1, 1, newColorPaint(color)); assertMatrixEq(new float[] { 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 3, 46, 5, 1 }, mModelViewMatrixUsed); canvas.save(); // Scaling canvas.scale(0.7f, 0.6f, 0.5f); canvas.drawLine(0, 0, 1, 1, newColorPaint(color)); assertMatrixEq(new float[] { 0.7f, 0, 0, 0, 0, -0.6f, 0, 0, 0, 0, 0.5f, 0, 3, 46, 5, 1 }, mModelViewMatrixUsed); // Rotation canvas.rotate(90, 0, 0, 1); canvas.drawLine(0, 0, 1, 1, newColorPaint(color)); assertMatrixEq(new float[] { 0, -0.6f, 0, 0, -0.7f, 0, 0, 0, 0, 0, 0.5f, 0, 3, 46, 5, 1 }, mModelViewMatrixUsed); canvas.restore(); // After restoring to the point just after translation, // do rotation again. canvas.rotate(180, 1, 0, 0); canvas.drawLine(0, 0, 1, 1, newColorPaint(color)); assertMatrixEq(new float[] { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 3, 46, 5, 1 }, mModelViewMatrixUsed); } } @SmallTest public void testClipRect() { // The test is currently broken, waiting for the fix // new ClipRectTest().run(); } private static class ClipRectTest extends GLStub { int mX, mY, mWidth, mHeight; @Override public void glScissor(int x, int y, int width, int height) { mX = x; mY = 100 - y - height; // flip in Y direction mWidth = width; mHeight = height; } private void assertClipRect(int x, int y, int width, int height) { assertEquals(x, mX); assertEquals(y, mY); assertEquals(width, mWidth); assertEquals(height, mHeight); } private void assertEmptyClipRect() { assertEquals(0, mWidth); assertEquals(0, mHeight); } void run() { GLCanvas canvas = new GLCanvasImpl(this); canvas.setSize(100, 100); canvas.save(); assertClipRect(0, 0, 100, 100); assertTrue(canvas.clipRect(10, 10, 70, 70)); canvas.save(); assertClipRect(10, 10, 60, 60); assertTrue(canvas.clipRect(30, 30, 90, 90)); canvas.save(); assertClipRect(30, 30, 40, 40); assertTrue(canvas.clipRect(40, 40, 60, 90)); assertClipRect(40, 40, 20, 30); assertFalse(canvas.clipRect(30, 30, 70, 40)); assertEmptyClipRect(); assertFalse(canvas.clipRect(0, 0, 100, 100)); assertEmptyClipRect(); canvas.restore(); assertClipRect(30, 30, 40, 40); canvas.restore(); assertClipRect(10, 10, 60, 60); canvas.restore(); assertClipRect(0, 0, 100, 100); canvas.translate(10, 20, 30); assertTrue(canvas.clipRect(10, 10, 70, 70)); canvas.save(); assertClipRect(20, 30, 60, 60); } } @SmallTest public void testSaveRestore() { new SaveRestoreTest().run(); } private static class SaveRestoreTest extends GLStub { int mX, mY, mWidth, mHeight; @Override public void glScissor(int x, int y, int width, int height) { mX = x; mY = 100 - y - height; // flip in Y direction mWidth = width; mHeight = height; } private void assertClipRect(int x, int y, int width, int height) { assertEquals(x, mX); assertEquals(y, mY); assertEquals(width, mWidth); assertEquals(height, mHeight); } void run() { GLCanvas canvas = new GLCanvasImpl(this); canvas.setSize(100, 100); canvas.setAlpha(0.7f); assertTrue(canvas.clipRect(10, 10, 70, 70)); canvas.save(canvas.SAVE_FLAG_CLIP); canvas.setAlpha(0.6f); assertTrue(canvas.clipRect(30, 30, 90, 90)); canvas.save(canvas.SAVE_FLAG_CLIP | canvas.SAVE_FLAG_ALPHA); canvas.setAlpha(0.5f); assertTrue(canvas.clipRect(40, 40, 60, 90)); assertEquals(0.5f, canvas.getAlpha()); assertClipRect(40, 40, 20, 30); canvas.restore(); // now both clipping rect and alpha are restored. assertEquals(0.6f, canvas.getAlpha()); assertClipRect(30, 30, 40, 40); canvas.restore(); // now only clipping rect is restored. canvas.save(0); canvas.save(0); canvas.restore(); canvas.restore(); assertEquals(0.6f, canvas.getAlpha()); assertTrue(canvas.clipRect(10, 10, 60, 60)); } } @SmallTest public void testDrawTexture() { new DrawTextureTest().run(); new DrawTextureMixedTest().run(); } private static class MyTexture extends BasicTexture { boolean mIsOpaque; int mBindCalled; MyTexture(GLCanvas canvas, int id, boolean isOpaque) { super(canvas, id, STATE_LOADED); setSize(1, 1); mIsOpaque = isOpaque; } @Override protected boolean onBind(GLCanvas canvas) { mBindCalled++; return true; } public boolean isOpaque() { return mIsOpaque; } } private static class DrawTextureTest extends GLMock { int mDrawTexiOESCalled; int mDrawArrayCalled; int[] mResult = new int[4]; @Override public void glDrawTexiOES(int x, int y, int z, int width, int height) { mDrawTexiOESCalled++; } @Override public void glDrawArrays(int mode, int first, int count) { assertNotNull(mGLVertexPointer); assertEquals(GL10.GL_TRIANGLE_STRIP, mode); assertEquals(4, count); mGLVertexPointer.bindByteBuffer(); double[] coord = new double[4]; mGLVertexPointer.getArrayElement(first, coord); mResult[0] = (int) coord[0]; mResult[1] = (int) coord[1]; mGLVertexPointer.getArrayElement(first + 1, coord); mResult[2] = (int) coord[0]; mResult[3] = (int) coord[1]; mDrawArrayCalled++; } void run() { GLCanvas canvas = new GLCanvasImpl(this); canvas.setSize(400, 300); MyTexture texture = new MyTexture(canvas, 42, false); // non-opaque MyTexture texture_o = new MyTexture(canvas, 47, true); // opaque // Draw a non-opaque texture canvas.drawTexture(texture, 100, 200, 300, 400); assertEquals(42, mGLBindTextureId); assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE_ENV_MODE)); assertPremultipliedBlending(this); assertFalse(mGLStencilEnabled); // Draw an opaque texture canvas.drawTexture(texture_o, 100, 200, 300, 400); assertEquals(47, mGLBindTextureId); assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE_ENV_MODE)); assertFalse(mGLBlendEnabled); // Draw a non-opaque texture with alpha = 0.5 canvas.setAlpha(0.5f); canvas.drawTexture(texture, 100, 200, 300, 400); assertEquals(42, mGLBindTextureId); assertEquals(0x80808080, mGLColor); assertEquals(GL_MODULATE, getTexEnvi(GL_TEXTURE_ENV_MODE)); assertPremultipliedBlending(this); assertFalse(mGLStencilEnabled); // Draw an non-opaque texture with overriden alpha = 1 canvas.drawTexture(texture, 100, 200, 300, 400, 1f); assertEquals(42, mGLBindTextureId); assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE_ENV_MODE)); assertPremultipliedBlending(this); // Draw an opaque texture with overriden alpha = 1 canvas.drawTexture(texture_o, 100, 200, 300, 400, 1f); assertEquals(47, mGLBindTextureId); assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE_ENV_MODE)); assertFalse(mGLBlendEnabled); // Draw an opaque texture with overridden alpha = 0.25 canvas.drawTexture(texture_o, 100, 200, 300, 400, 0.25f); assertEquals(47, mGLBindTextureId); assertEquals(0x40404040, mGLColor); assertEquals(GL_MODULATE, getTexEnvi(GL_TEXTURE_ENV_MODE)); assertPremultipliedBlending(this); // Draw an opaque texture with overridden alpha = 0.125 // but with some rotation so it will use DrawArray. canvas.save(); canvas.rotate(30, 0, 0, 1); canvas.drawTexture(texture_o, 100, 200, 300, 400, 0.125f); canvas.restore(); assertEquals(47, mGLBindTextureId); assertEquals(0x20202020, mGLColor); assertEquals(GL_MODULATE, getTexEnvi(GL_TEXTURE_ENV_MODE)); assertPremultipliedBlending(this); // We have drawn seven textures above. assertEquals(1, mDrawArrayCalled); assertEquals(6, mDrawTexiOESCalled); // translate and scale does not affect whether we // can use glDrawTexiOES, but rotate may. canvas.translate(10, 20, 30); canvas.drawTexture(texture, 100, 200, 300, 400); assertEquals(7, mDrawTexiOESCalled); canvas.scale(10, 20, 30); canvas.drawTexture(texture, 100, 200, 300, 400); assertEquals(8, mDrawTexiOESCalled); canvas.rotate(90, 1, 2, 3); canvas.drawTexture(texture, 100, 200, 300, 400); assertEquals(8, mDrawTexiOESCalled); canvas.rotate(-90, 1, 2, 3); canvas.drawTexture(texture, 100, 200, 300, 400); assertEquals(9, mDrawTexiOESCalled); canvas.rotate(180, 0, 0, 1); canvas.drawTexture(texture, 100, 200, 300, 400); assertEquals(9, mDrawTexiOESCalled); canvas.rotate(180, 0, 0, 1); canvas.drawTexture(texture, 100, 200, 300, 400); assertEquals(10, mDrawTexiOESCalled); assertEquals(3, mDrawArrayCalled); assertTrue(texture.isLoaded(canvas)); texture.recycle(); assertFalse(texture.isLoaded(canvas)); canvas.deleteRecycledResources(); assertTrue(texture_o.isLoaded(canvas)); texture_o.recycle(); assertFalse(texture_o.isLoaded(canvas)); } } private static class DrawTextureMixedTest extends GLMock { boolean mTexture2DEnabled0, mTexture2DEnabled1; int mBindTexture0; int mBindTexture1; @Override public void glEnable(int cap) { if (cap == GL_TEXTURE_2D) { texture2DEnable(true); } } @Override public void glDisable(int cap) { if (cap == GL_TEXTURE_2D) { texture2DEnable(false); } } private void texture2DEnable(boolean enable) { if (mGLActiveTexture == GL_TEXTURE0) { mTexture2DEnabled0 = enable; } else if (mGLActiveTexture == GL_TEXTURE1) { mTexture2DEnabled1 = enable; } else { fail(); } } @Override public void glTexEnvfv(int target, int pname, float[] params, int offset) { if (target == GL_TEXTURE_ENV && pname == GL_TEXTURE_ENV_COLOR) { assertEquals(0.5f, params[offset + 3]); } } @Override public void glBindTexture(int target, int texture) { if (target == GL_TEXTURE_2D) { if (mGLActiveTexture == GL_TEXTURE0) { mBindTexture0 = texture; } else if (mGLActiveTexture == GL_TEXTURE1) { mBindTexture1 = texture; } else { fail(); } } } void run() { GLCanvas canvas = new GLCanvasImpl(this); canvas.setSize(400, 300); MyTexture from = new MyTexture(canvas, 42, false); // non-opaque MyTexture to = new MyTexture(canvas, 47, true); // opaque canvas.drawMixed(from, to, 0.5f, 100, 200, 300, 400); assertEquals(42, mBindTexture0); assertEquals(47, mBindTexture1); assertTrue(mTexture2DEnabled0); assertFalse(mTexture2DEnabled1); assertEquals(GL_COMBINE, getTexEnvi(GL_TEXTURE1, GL_TEXTURE_ENV_MODE)); assertEquals(GL_INTERPOLATE, getTexEnvi(GL_TEXTURE1, GL_COMBINE_RGB)); assertEquals(GL_INTERPOLATE, getTexEnvi(GL_TEXTURE1, GL_COMBINE_ALPHA)); assertEquals(GL_CONSTANT, getTexEnvi(GL_TEXTURE1, GL_SRC2_RGB)); assertEquals(GL_CONSTANT, getTexEnvi(GL_TEXTURE1, GL_SRC2_ALPHA)); assertEquals(GL_SRC_ALPHA, getTexEnvi(GL_TEXTURE1, GL_OPERAND2_RGB)); assertEquals(GL_SRC_ALPHA, getTexEnvi(GL_TEXTURE1, GL_OPERAND2_ALPHA)); assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE0, GL_TEXTURE_ENV_MODE)); assertFalse(mGLBlendEnabled); canvas.drawMixed(from, to, 0, 100, 200, 300, 400); assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE0, GL_TEXTURE_ENV_MODE)); assertEquals(42, mBindTexture0); canvas.drawMixed(from, to, 1, 100, 200, 300, 400); assertEquals(GL_REPLACE, getTexEnvi(GL_TEXTURE0, GL_TEXTURE_ENV_MODE)); assertEquals(47, mBindTexture0); } } @SmallTest public void testGetGLInstance() { GL11 glStub = new GLStub(); GLCanvas canvas = new GLCanvasImpl(glStub); assertSame(glStub, canvas.getGLInstance()); } private static void assertPremultipliedBlending(GLMock mock) { assertTrue(mock.mGLBlendFuncCalled > 0); assertTrue(mock.mGLBlendEnabled); assertEquals(GL11.GL_ONE, mock.mGLBlendFuncSFactor); assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, mock.mGLBlendFuncDFactor); } private static void assertMatrixEq(float[] expected, float[] actual) { try { for (int i = 0; i < 16; i++) { assertFloatEq(expected[i], actual[i]); } } catch (Throwable t) { Log.v(TAG, "expected = " + Arrays.toString(expected) + ", actual = " + Arrays.toString(actual)); fail(); } } public static void assertFloatEq(float expected, float actual) { if (Math.abs(actual - expected) > 1e-6) { Log.v(TAG, "expected: " + expected + ", actual: " + actual); fail(); } } }