1 /* 2 * Copyright 2015 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 package org.webrtc; 12 13 import static org.junit.Assert.assertEquals; 14 import static org.junit.Assert.assertTrue; 15 import static org.junit.Assert.fail; 16 17 import android.opengl.GLES20; 18 import android.support.test.filters.MediumTest; 19 import android.support.test.filters.SmallTest; 20 import java.nio.ByteBuffer; 21 import java.util.Random; 22 import org.chromium.base.test.BaseJUnit4ClassRunner; 23 import org.junit.Test; 24 import org.junit.runner.RunWith; 25 26 @RunWith(BaseJUnit4ClassRunner.class) 27 public class GlRectDrawerTest { 28 // Resolution of the test image. 29 private static final int WIDTH = 16; 30 private static final int HEIGHT = 16; 31 // Seed for random pixel creation. 32 private static final int SEED = 42; 33 // When comparing pixels, allow some slack for float arithmetic and integer rounding. 34 private static final float MAX_DIFF = 1.5f; 35 36 // clang-format off 37 private static final float[] IDENTITY_MATRIX = { 38 1, 0, 0, 0, 39 0, 1, 0, 0, 40 0, 0, 1, 0, 41 0, 0, 0, 1}; 42 // clang-format on 43 normalizedByte(byte b)44 private static float normalizedByte(byte b) { 45 return (b & 0xFF) / 255.0f; 46 } 47 saturatedConvert(float c)48 private static float saturatedConvert(float c) { 49 return 255.0f * Math.max(0, Math.min(c, 1)); 50 } 51 52 // Assert RGB ByteBuffers are pixel perfect identical. assertByteBufferEquals( int width, int height, ByteBuffer actual, ByteBuffer expected)53 private static void assertByteBufferEquals( 54 int width, int height, ByteBuffer actual, ByteBuffer expected) { 55 actual.rewind(); 56 expected.rewind(); 57 assertEquals(actual.remaining(), width * height * 3); 58 assertEquals(expected.remaining(), width * height * 3); 59 for (int y = 0; y < height; ++y) { 60 for (int x = 0; x < width; ++x) { 61 final int actualR = actual.get() & 0xFF; 62 final int actualG = actual.get() & 0xFF; 63 final int actualB = actual.get() & 0xFF; 64 final int expectedR = expected.get() & 0xFF; 65 final int expectedG = expected.get() & 0xFF; 66 final int expectedB = expected.get() & 0xFF; 67 if (actualR != expectedR || actualG != expectedG || actualB != expectedB) { 68 fail("ByteBuffers of size " + width + "x" + height + " not equal at position " 69 + "(" + x + ", " + y + "). Expected color (R,G,B): " 70 + "(" + expectedR + ", " + expectedG + ", " + expectedB + ")" 71 + " but was: " 72 + "(" + actualR + ", " + actualG + ", " + actualB + ")."); 73 } 74 } 75 } 76 } 77 78 // Convert RGBA ByteBuffer to RGB ByteBuffer. stripAlphaChannel(ByteBuffer rgbaBuffer)79 private static ByteBuffer stripAlphaChannel(ByteBuffer rgbaBuffer) { 80 rgbaBuffer.rewind(); 81 assertEquals(rgbaBuffer.remaining() % 4, 0); 82 final int numberOfPixels = rgbaBuffer.remaining() / 4; 83 final ByteBuffer rgbBuffer = ByteBuffer.allocateDirect(numberOfPixels * 3); 84 while (rgbaBuffer.hasRemaining()) { 85 // Copy RGB. 86 for (int channel = 0; channel < 3; ++channel) { 87 rgbBuffer.put(rgbaBuffer.get()); 88 } 89 // Drop alpha. 90 rgbaBuffer.get(); 91 } 92 return rgbBuffer; 93 } 94 95 // TODO(titovartem) make correct fix during webrtc:9175 96 @SuppressWarnings("ByteBufferBackingArray") 97 @Test 98 @SmallTest testRgbRendering()99 public void testRgbRendering() { 100 // Create EGL base with a pixel buffer as display output. 101 final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER); 102 eglBase.createPbufferSurface(WIDTH, HEIGHT); 103 eglBase.makeCurrent(); 104 105 // Create RGB byte buffer plane with random content. 106 final ByteBuffer rgbPlane = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 3); 107 final Random random = new Random(SEED); 108 random.nextBytes(rgbPlane.array()); 109 110 // Upload the RGB byte buffer data as a texture. 111 final int rgbTexture = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D); 112 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 113 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, rgbTexture); 114 GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, WIDTH, HEIGHT, 0, GLES20.GL_RGB, 115 GLES20.GL_UNSIGNED_BYTE, rgbPlane); 116 GlUtil.checkNoGLES2Error("glTexImage2D"); 117 118 // Draw the RGB frame onto the pixel buffer. 119 final GlRectDrawer drawer = new GlRectDrawer(); 120 drawer.drawRgb(rgbTexture, IDENTITY_MATRIX, WIDTH, HEIGHT, 0 /* viewportX */, 0 /* viewportY */, 121 WIDTH, HEIGHT); 122 123 // Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9. 124 final ByteBuffer rgbaData = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 4); 125 GLES20.glReadPixels(0, 0, WIDTH, HEIGHT, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, rgbaData); 126 GlUtil.checkNoGLES2Error("glReadPixels"); 127 128 // Assert rendered image is pixel perfect to source RGB. 129 assertByteBufferEquals(WIDTH, HEIGHT, stripAlphaChannel(rgbaData), rgbPlane); 130 131 drawer.release(); 132 GLES20.glDeleteTextures(1, new int[] {rgbTexture}, 0); 133 eglBase.release(); 134 } 135 136 // TODO(titovartem) make correct fix during webrtc:9175 137 @SuppressWarnings("ByteBufferBackingArray") 138 @Test 139 @SmallTest testYuvRendering()140 public void testYuvRendering() { 141 // Create EGL base with a pixel buffer as display output. 142 EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER); 143 eglBase.createPbufferSurface(WIDTH, HEIGHT); 144 eglBase.makeCurrent(); 145 146 // Create YUV byte buffer planes with random content. 147 final ByteBuffer[] yuvPlanes = new ByteBuffer[3]; 148 final Random random = new Random(SEED); 149 for (int i = 0; i < 3; ++i) { 150 yuvPlanes[i] = ByteBuffer.allocateDirect(WIDTH * HEIGHT); 151 random.nextBytes(yuvPlanes[i].array()); 152 } 153 154 // Generate 3 texture ids for Y/U/V. 155 final int yuvTextures[] = new int[3]; 156 for (int i = 0; i < 3; i++) { 157 yuvTextures[i] = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D); 158 } 159 160 // Upload the YUV byte buffer data as textures. 161 for (int i = 0; i < 3; ++i) { 162 GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i); 163 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]); 164 GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, WIDTH, HEIGHT, 0, 165 GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, yuvPlanes[i]); 166 GlUtil.checkNoGLES2Error("glTexImage2D"); 167 } 168 169 // Draw the YUV frame onto the pixel buffer. 170 final GlRectDrawer drawer = new GlRectDrawer(); 171 drawer.drawYuv(yuvTextures, IDENTITY_MATRIX, WIDTH, HEIGHT, 0 /* viewportX */, 172 0 /* viewportY */, WIDTH, HEIGHT); 173 174 // Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9. 175 final ByteBuffer data = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 4); 176 GLES20.glReadPixels(0, 0, WIDTH, HEIGHT, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, data); 177 GlUtil.checkNoGLES2Error("glReadPixels"); 178 179 // Compare the YUV data with the RGBA result. 180 for (int y = 0; y < HEIGHT; ++y) { 181 for (int x = 0; x < WIDTH; ++x) { 182 // YUV color space. Y in [0, 1], UV in [-0.5, 0.5]. The constants are taken from the YUV 183 // fragment shader code in GlGenericDrawer. 184 final float y_luma = normalizedByte(yuvPlanes[0].get()); 185 final float u_chroma = normalizedByte(yuvPlanes[1].get()); 186 final float v_chroma = normalizedByte(yuvPlanes[2].get()); 187 // Expected color in unrounded RGB [0.0f, 255.0f]. 188 final float expectedRed = 189 saturatedConvert(1.16438f * y_luma + 1.59603f * v_chroma - 0.874202f); 190 final float expectedGreen = saturatedConvert( 191 1.16438f * y_luma - 0.391762f * u_chroma - 0.812968f * v_chroma + 0.531668f); 192 final float expectedBlue = 193 saturatedConvert(1.16438f * y_luma + 2.01723f * u_chroma - 1.08563f); 194 195 // Actual color in RGB8888. 196 final int actualRed = data.get() & 0xFF; 197 final int actualGreen = data.get() & 0xFF; 198 final int actualBlue = data.get() & 0xFF; 199 final int actualAlpha = data.get() & 0xFF; 200 201 // Assert rendered image is close to pixel perfect from source YUV. 202 assertTrue(Math.abs(actualRed - expectedRed) < MAX_DIFF); 203 assertTrue(Math.abs(actualGreen - expectedGreen) < MAX_DIFF); 204 assertTrue(Math.abs(actualBlue - expectedBlue) < MAX_DIFF); 205 assertEquals(actualAlpha, 255); 206 } 207 } 208 209 drawer.release(); 210 GLES20.glDeleteTextures(3, yuvTextures, 0); 211 eglBase.release(); 212 } 213 214 /** 215 * The purpose here is to test GlRectDrawer.oesDraw(). Unfortunately, there is no easy way to 216 * create an OES texture, which is needed for input to oesDraw(). Most of the test is concerned 217 * with creating OES textures in the following way: 218 * - Create SurfaceTexture with help from SurfaceTextureHelper. 219 * - Create an EglBase with the SurfaceTexture as EGLSurface. 220 * - Upload RGB texture with known content. 221 * - Draw the RGB texture onto the EglBase with the SurfaceTexture as target. 222 * - Wait for an OES texture to be produced. 223 * The actual oesDraw() test is this: 224 * - Create an EglBase with a pixel buffer as target. 225 * - Render the OES texture onto the pixel buffer. 226 * - Read back the pixel buffer and compare it with the known RGB data. 227 */ 228 // TODO(titovartem) make correct fix during webrtc:9175 229 @SuppressWarnings("ByteBufferBackingArray") 230 @Test 231 @MediumTest 232 public void testOesRendering() throws InterruptedException { 233 /** 234 * Stub class to convert RGB ByteBuffers to OES textures by drawing onto a SurfaceTexture. 235 */ 236 class StubOesTextureProducer { 237 private final EglBase eglBase; 238 private final GlRectDrawer drawer; 239 private final int rgbTexture; 240 241 public StubOesTextureProducer(EglBase.Context sharedContext, 242 SurfaceTextureHelper surfaceTextureHelper, int width, int height) { 243 eglBase = EglBase.create(sharedContext, EglBase.CONFIG_PLAIN); 244 surfaceTextureHelper.setTextureSize(width, height); 245 eglBase.createSurface(surfaceTextureHelper.getSurfaceTexture()); 246 assertEquals(eglBase.surfaceWidth(), width); 247 assertEquals(eglBase.surfaceHeight(), height); 248 249 drawer = new GlRectDrawer(); 250 251 eglBase.makeCurrent(); 252 rgbTexture = GlUtil.generateTexture(GLES20.GL_TEXTURE_2D); 253 } 254 255 public void draw(ByteBuffer rgbPlane) { 256 eglBase.makeCurrent(); 257 258 // Upload RGB data to texture. 259 GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 260 GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, rgbTexture); 261 GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGB, WIDTH, HEIGHT, 0, GLES20.GL_RGB, 262 GLES20.GL_UNSIGNED_BYTE, rgbPlane); 263 // Draw the RGB data onto the SurfaceTexture. 264 drawer.drawRgb(rgbTexture, IDENTITY_MATRIX, WIDTH, HEIGHT, 0 /* viewportX */, 265 0 /* viewportY */, WIDTH, HEIGHT); 266 eglBase.swapBuffers(); 267 } 268 269 public void release() { 270 eglBase.makeCurrent(); 271 drawer.release(); 272 GLES20.glDeleteTextures(1, new int[] {rgbTexture}, 0); 273 eglBase.release(); 274 } 275 } 276 277 // Create EGL base with a pixel buffer as display output. 278 final EglBase eglBase = EglBase.create(null, EglBase.CONFIG_PIXEL_BUFFER); 279 eglBase.createPbufferSurface(WIDTH, HEIGHT); 280 281 // Create resources for generating OES textures. 282 final SurfaceTextureHelper surfaceTextureHelper = SurfaceTextureHelper.create( 283 "SurfaceTextureHelper test" /* threadName */, eglBase.getEglBaseContext()); 284 final StubOesTextureProducer oesProducer = new StubOesTextureProducer( 285 eglBase.getEglBaseContext(), surfaceTextureHelper, WIDTH, HEIGHT); 286 final SurfaceTextureHelperTest.MockTextureListener listener = 287 new SurfaceTextureHelperTest.MockTextureListener(); 288 surfaceTextureHelper.startListening(listener); 289 290 // Create RGB byte buffer plane with random content. 291 final ByteBuffer rgbPlane = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 3); 292 final Random random = new Random(SEED); 293 random.nextBytes(rgbPlane.array()); 294 295 // Draw the frame and block until an OES texture is delivered. 296 oesProducer.draw(rgbPlane); 297 final VideoFrame.TextureBuffer textureBuffer = listener.waitForTextureBuffer(); 298 299 // Real test starts here. 300 // Draw the OES texture on the pixel buffer. 301 eglBase.makeCurrent(); 302 final GlRectDrawer drawer = new GlRectDrawer(); 303 drawer.drawOes(textureBuffer.getTextureId(), 304 RendererCommon.convertMatrixFromAndroidGraphicsMatrix(textureBuffer.getTransformMatrix()), 305 WIDTH, HEIGHT, 0 /* viewportX */, 0 /* viewportY */, WIDTH, HEIGHT); 306 307 // Download the pixels in the pixel buffer as RGBA. Not all platforms support RGB, e.g. Nexus 9. 308 final ByteBuffer rgbaData = ByteBuffer.allocateDirect(WIDTH * HEIGHT * 4); 309 GLES20.glReadPixels(0, 0, WIDTH, HEIGHT, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, rgbaData); 310 GlUtil.checkNoGLES2Error("glReadPixels"); 311 312 // Assert rendered image is pixel perfect to source RGB. 313 assertByteBufferEquals(WIDTH, HEIGHT, stripAlphaChannel(rgbaData), rgbPlane); 314 315 drawer.release(); 316 textureBuffer.release(); 317 oesProducer.release(); 318 surfaceTextureHelper.dispose(); 319 eglBase.release(); 320 } 321 } 322