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