• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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