1 /* 2 * libjingle 3 * Copyright 2015 Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 package org.webrtc; 28 29 import android.graphics.SurfaceTexture; 30 import android.opengl.GLES20; 31 import android.test.ActivityTestCase; 32 import android.test.suitebuilder.annotation.MediumTest; 33 import android.test.suitebuilder.annotation.SmallTest; 34 35 import java.nio.ByteBuffer; 36 import java.util.Random; 37 38 public final class GlRectDrawerTest extends ActivityTestCase { 39 // Resolution of the test image. 40 private static final int WIDTH = 16; 41 private static final int HEIGHT = 16; 42 // Seed for random pixel creation. 43 private static final int SEED = 42; 44 // When comparing pixels, allow some slack for float arithmetic and integer rounding. 45 private static final float MAX_DIFF = 1.5f; 46 normalizedByte(byte b)47 private static float normalizedByte(byte b) { 48 return (b & 0xFF) / 255.0f; 49 } 50 saturatedConvert(float c)51 private static float saturatedConvert(float c) { 52 return 255.0f * Math.max(0, Math.min(c, 1)); 53 } 54 55 // Assert RGB ByteBuffers are pixel perfect identical. assertEquals(int width, int height, ByteBuffer actual, ByteBuffer expected)56 private static void assertEquals(int width, int height, ByteBuffer actual, ByteBuffer expected) { 57 actual.rewind(); 58 expected.rewind(); 59 assertEquals(actual.remaining(), width * height * 3); 60 assertEquals(expected.remaining(), width * height * 3); 61 for (int y = 0; y < height; ++y) { 62 for (int x = 0; x < width; ++x) { 63 final int actualR = actual.get() & 0xFF; 64 final int actualG = actual.get() & 0xFF; 65 final int actualB = actual.get() & 0xFF; 66 final int expectedR = expected.get() & 0xFF; 67 final int expectedG = expected.get() & 0xFF; 68 final int expectedB = expected.get() & 0xFF; 69 if (actualR != expectedR || actualG != expectedG || actualB != expectedB) { 70 fail("ByteBuffers of size " + width + "x" + height + " not equal at position " 71 + "(" + x + ", " + y + "). Expected color (R,G,B): " 72 + "(" + expectedR + ", " + expectedG + ", " + expectedB + ")" 73 + " but was: " + "(" + actualR + ", " + actualG + ", " + actualB + ")."); 74 } 75 } 76 } 77 } 78 79 // Convert RGBA ByteBuffer to RGB ByteBuffer. stripAlphaChannel(ByteBuffer rgbaBuffer)80 private static ByteBuffer stripAlphaChannel(ByteBuffer rgbaBuffer) { 81 rgbaBuffer.rewind(); 82 assertEquals(rgbaBuffer.remaining() % 4, 0); 83 final int numberOfPixels = rgbaBuffer.remaining() / 4; 84 final ByteBuffer rgbBuffer = ByteBuffer.allocateDirect(numberOfPixels * 3); 85 while (rgbaBuffer.hasRemaining()) { 86 // Copy RGB. 87 for (int channel = 0; channel < 3; ++channel) { 88 rgbBuffer.put(rgbaBuffer.get()); 89 } 90 // Drop alpha. 91 rgbaBuffer.get(); 92 } 93 return rgbBuffer; 94 } 95 96 @SmallTest testRgbRendering()97 public void testRgbRendering() { 98 // Create EGL base with a pixel buffer as display output. 99 final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER); 100 eglBase.createPbufferSurface(WIDTH, HEIGHT); 101 eglBase.makeCurrent(); 102 103 // Create RGB byte buffer plane with random content. 104 final ByteBuffer rgbPlane = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 3); 105 final Random random = new Random(SEED); 106 random.nextBytes(rgbPlane.array()); 107 108 // Upload the RGB byte buffer data as a texture. 109 final int rgbTexture = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D); 110 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 111 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, rgbTexture); 112 GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, WIDTH, 113 HEIGHT, 0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_BYTE, rgbPlane); 114 GlUtil.checkNoGLES2Error("glTexImage2D"); 115 116 // Draw the RGB frame onto the pixel buffer. 117 final GlRectDrawer drawer = new GlRectDrawer(); 118 drawer.drawRgb(rgbTexture, RendererCommon.identityMatrix(), 0, 0, WIDTH, HEIGHT); 119 120 // Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9. 121 final ByteBuffer rgbaData = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 4); 122 GLES20.glReadPixels(0, 0, WIDTH, HEIGHT, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, rgbaData); 123 GlUtil.checkNoGLES2Error("glReadPixels"); 124 125 // Assert rendered image is pixel perfect to source RGB. 126 assertEquals(WIDTH, HEIGHT, stripAlphaChannel(rgbaData), rgbPlane); 127 128 drawer.release(); 129 GLES20.glDeleteTextures(1, new int[] {rgbTexture}, 0); 130 eglBase.release(); 131 } 132 133 @SmallTest testYuvRendering()134 public void testYuvRendering() { 135 // Create EGL base with a pixel buffer as display output. 136 EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER); 137 eglBase.createPbufferSurface(WIDTH, HEIGHT); 138 eglBase.makeCurrent(); 139 140 // Create YUV byte buffer planes with random content. 141 final ByteBuffer[] yuvPlanes = new ByteBuffer[3]; 142 final Random random = new Random(SEED); 143 for (int i = 0; i < 3; ++i) { 144 yuvPlanes[i] = ByteBuffer.allocateDirect(WIDTH * HEIGHT); 145 random.nextBytes(yuvPlanes[i].array()); 146 } 147 148 // Generate 3 texture ids for Y/U/V. 149 final int yuvTextures[] = new int[3]; 150 for (int i = 0; i < 3; i++) { 151 yuvTextures[i] = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D); 152 } 153 154 // Upload the YUV byte buffer data as textures. 155 for (int i = 0; i < 3; ++i) { 156 GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); 157 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); 158 GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, WIDTH, 159 HEIGHT, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, yuvPlanes[i]); 160 GlUtil.checkNoGLES2Error("glTexImage2D"); 161 } 162 163 // Draw the YUV frame onto the pixel buffer. 164 final GlRectDrawer drawer = new GlRectDrawer(); 165 drawer.drawYuv(yuvTextures, RendererCommon.identityMatrix(), 0, 0, WIDTH, HEIGHT); 166 167 // Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9. 168 final ByteBuffer data = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 4); 169 GLES20.glReadPixels(0, 0, WIDTH, HEIGHT, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, data); 170 GlUtil.checkNoGLES2Error("glReadPixels"); 171 172 // Compare the YUV data with the RGBA result. 173 for (int y = 0; y < HEIGHT; ++y) { 174 for (int x = 0; x < WIDTH; ++x) { 175 // YUV color space. Y in [0, 1], UV in [-0.5, 0.5]. The constants are taken from the YUV 176 // fragment shader code in GlRectDrawer. 177 final float y_luma = normalizedByte(yuvPlanes[0].get()); 178 final float u_chroma = normalizedByte(yuvPlanes[1].get()) - 0.5f; 179 final float v_chroma = normalizedByte(yuvPlanes[2].get()) - 0.5f; 180 // Expected color in unrounded RGB [0.0f, 255.0f]. 181 final float expectedRed = saturatedConvert(y_luma + 1.403f * v_chroma); 182 final float expectedGreen = 183 saturatedConvert(y_luma - 0.344f * u_chroma - 0.714f * v_chroma); 184 final float expectedBlue = saturatedConvert(y_luma + 1.77f * u_chroma); 185 186 // Actual color in RGB8888. 187 final int actualRed = data.get() & 0xFF; 188 final int actualGreen = data.get() & 0xFF; 189 final int actualBlue = data.get() & 0xFF; 190 final int actualAlpha = data.get() & 0xFF; 191 192 // Assert rendered image is close to pixel perfect from source YUV. 193 assertTrue(Math.abs(actualRed - expectedRed) < MAX_DIFF); 194 assertTrue(Math.abs(actualGreen - expectedGreen) < MAX_DIFF); 195 assertTrue(Math.abs(actualBlue - expectedBlue) < MAX_DIFF); 196 assertEquals(actualAlpha, 255); 197 } 198 } 199 200 drawer.release(); 201 GLES20.glDeleteTextures(3, yuvTextures, 0); 202 eglBase.release(); 203 } 204 205 /** 206 * The purpose here is to test GlRectDrawer.oesDraw(). Unfortunately, there is no easy way to 207 * create an OES texture, which is needed for input to oesDraw(). Most of the test is concerned 208 * with creating OES textures in the following way: 209 * - Create SurfaceTexture with help from SurfaceTextureHelper. 210 * - Create an EglBase with the SurfaceTexture as EGLSurface. 211 * - Upload RGB texture with known content. 212 * - Draw the RGB texture onto the EglBase with the SurfaceTexture as target. 213 * - Wait for an OES texture to be produced. 214 * The actual oesDraw() test is this: 215 * - Create an EglBase with a pixel buffer as target. 216 * - Render the OES texture onto the pixel buffer. 217 * - Read back the pixel buffer and compare it with the known RGB data. 218 */ 219 @MediumTest 220 public void testOesRendering() throws InterruptedException { 221 /** 222 * Stub class to convert RGB ByteBuffers to OES textures by drawing onto a SurfaceTexture. 223 */ 224 class StubOesTextureProducer { 225 private final EglBase eglBase; 226 private final GlRectDrawer drawer; 227 private final int rgbTexture; 228 229 public StubOesTextureProducer( 230 EglBase.Context sharedContext, SurfaceTexture surfaceTexture, int width, 231 int height) { 232 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PLAIN); 233 surfaceTexture.setDefaultBufferSize(width, height); 234 eglBase.createSurface(surfaceTexture); 235 assertEquals(eglBase.surfaceWidth(), width); 236 assertEquals(eglBase.surfaceHeight(), height); 237 238 drawer = new GlRectDrawer(); 239 240 eglBase.makeCurrent(); 241 rgbTexture = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D); 242 } 243 244 public void draw(ByteBuffer rgbPlane) { 245 eglBase.makeCurrent(); 246 247 // Upload RGB data to texture. 248 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 249 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, rgbTexture); 250 GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, WIDTH, 251 HEIGHT, 0, GLES20.GL_RGB, GLES20.GL_UNSIGNED_BYTE, rgbPlane); 252 // Draw the RGB data onto the SurfaceTexture. 253 drawer.drawRgb(rgbTexture, RendererCommon.identityMatrix(), 0, 0, WIDTH, HEIGHT); 254 eglBase.swapBuffers(); 255 } 256 257 public void release() { 258 eglBase.makeCurrent(); 259 drawer.release(); 260 GLES20.glDeleteTextures(1, new int[] {rgbTexture}, 0); 261 eglBase.release(); 262 } 263 } 264 265 // Create EGL base with a pixel buffer as display output. 266 final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER); 267 eglBase.createPbufferSurface(WIDTH, HEIGHT); 268 269 // Create resources for generating OES textures. 270 final SurfaceTextureHelper surfaceTextureHelper = 271 SurfaceTextureHelper.create(eglBase.getEglBaseContext()); 272 final StubOesTextureProducer oesProducer = new StubOesTextureProducer( 273 eglBase.getEglBaseContext(), surfaceTextureHelper.getSurfaceTexture(), WIDTH, HEIGHT); 274 final SurfaceTextureHelperTest.MockTextureListener listener = 275 new SurfaceTextureHelperTest.MockTextureListener(); 276 surfaceTextureHelper.setListener(listener); 277 278 // Create RGB byte buffer plane with random content. 279 final ByteBuffer rgbPlane = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 3); 280 final Random random = new Random(SEED); 281 random.nextBytes(rgbPlane.array()); 282 283 // Draw the frame and block until an OES texture is delivered. 284 oesProducer.draw(rgbPlane); 285 listener.waitForNewFrame(); 286 287 // Real test starts here. 288 // Draw the OES texture on the pixel buffer. 289 eglBase.makeCurrent(); 290 final GlRectDrawer drawer = new GlRectDrawer(); 291 drawer.drawOes(listener.oesTextureId, listener.transformMatrix, 0, 0, WIDTH, HEIGHT); 292 293 // Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9. 294 final ByteBuffer rgbaData = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 4); 295 GLES20.glReadPixels(0, 0, WIDTH, HEIGHT, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, rgbaData); 296 GlUtil.checkNoGLES2Error("glReadPixels"); 297 298 // Assert rendered image is pixel perfect to source RGB. 299 assertEquals(WIDTH, HEIGHT, stripAlphaChannel(rgbaData), rgbPlane); 300 301 drawer.release(); 302 surfaceTextureHelper.returnTextureFrame(); 303 oesProducer.release(); 304 surfaceTextureHelper.disconnect(); 305 eglBase.release(); 306 } 307 } 308