xref: /aosp_15_r20/external/angle/src/tests/gl_tests/WebGLReadOutsideFramebufferTest.cpp (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1 //
2 // Copyright 2017 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 
7 // WebGLReadOutsideFramebufferTest.cpp : Test functions which read the framebuffer (readPixels,
8 // copyTexSubImage2D, copyTexImage2D) on areas outside the framebuffer.
9 
10 #include "test_utils/ANGLETest.h"
11 
12 #include "test_utils/gl_raii.h"
13 
14 namespace
15 {
16 
17 class PixelRect
18 {
19   public:
PixelRect(int width,int height)20     PixelRect(int width, int height) : mWidth(width), mHeight(height), mData(width * height) {}
21 
22     // Set each pixel to a different color consisting of the x,y position and a given tag.
23     // Making each pixel a different means any misplaced pixel will cause a failure.
24     // Encoding the position proved valuable in debugging.
fill(unsigned tag)25     void fill(unsigned tag)
26     {
27         for (int x = 0; x < mWidth; ++x)
28         {
29             for (int y = 0; y < mHeight; ++y)
30             {
31                 mData[x + y * mWidth] = angle::GLColor(x + (y << 8) + (tag << 16));
32             }
33         }
34     }
35 
toTexture2D(GLuint target,GLuint texid) const36     void toTexture2D(GLuint target, GLuint texid) const
37     {
38         glBindTexture(target, texid);
39         if (target == GL_TEXTURE_CUBE_MAP)
40         {
41             glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, mWidth, mHeight, 0, GL_RGBA,
42                          GL_UNSIGNED_BYTE, mData.data());
43             glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGBA, mWidth, mHeight, 0, GL_RGBA,
44                          GL_UNSIGNED_BYTE, mData.data());
45             glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGBA, mWidth, mHeight, 0, GL_RGBA,
46                          GL_UNSIGNED_BYTE, mData.data());
47             glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGBA, mWidth, mHeight, 0, GL_RGBA,
48                          GL_UNSIGNED_BYTE, mData.data());
49             glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGBA, mWidth, mHeight, 0, GL_RGBA,
50                          GL_UNSIGNED_BYTE, mData.data());
51             glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGBA, mWidth, mHeight, 0, GL_RGBA,
52                          GL_UNSIGNED_BYTE, mData.data());
53         }
54         else
55         {
56             ASSERT_GLENUM_EQ(GL_TEXTURE_2D, target);
57             glTexImage2D(target, 0, GL_RGBA, mWidth, mHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
58                          mData.data());
59         }
60         glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
61         glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
62         glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
63         glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
64     }
65 
toTexture3D(GLuint target,GLuint texid,GLint depth) const66     void toTexture3D(GLuint target, GLuint texid, GLint depth) const
67     {
68         glBindTexture(target, texid);
69 
70         glTexImage3D(target, 0, GL_RGBA, mWidth, mHeight, depth, 0, GL_RGBA, GL_UNSIGNED_BYTE,
71                      nullptr);
72         for (GLint z = 0; z < depth; z++)
73         {
74             glTexSubImage3D(target, 0, 0, 0, z, mWidth, mHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE,
75                             mData.data());
76         }
77         glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
78         glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
79         glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
80         glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
81         glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
82     }
83 
readFB(int x,int y)84     void readFB(int x, int y)
85     {
86         glReadPixels(x, y, mWidth, mHeight, GL_RGBA, GL_UNSIGNED_BYTE, mData.data());
87     }
88 
89     // Read pixels from 'other' into 'this' from position (x,y).
90     // Pixels outside 'other' are untouched or zeroed according to 'zeroOutside.'
readPixelRect(const PixelRect & other,int x,int y,bool zeroOutside)91     void readPixelRect(const PixelRect &other, int x, int y, bool zeroOutside)
92     {
93         for (int i = 0; i < mWidth; ++i)
94         {
95             for (int j = 0; j < mHeight; ++j)
96             {
97                 angle::GLColor *dest = &mData[i + j * mWidth];
98                 if (!other.getPixel(x + i, y + j, dest) && zeroOutside)
99                 {
100                     *dest = angle::GLColor(0);
101                 }
102             }
103         }
104     }
105 
getPixel(int x,int y,angle::GLColor * colorOut) const106     bool getPixel(int x, int y, angle::GLColor *colorOut) const
107     {
108         if (0 <= x && x < mWidth && 0 <= y && y < mHeight)
109         {
110             *colorOut = mData[x + y * mWidth];
111             return true;
112         }
113         return false;
114     }
115 
compare(const PixelRect & expected) const116     void compare(const PixelRect &expected) const
117     {
118         ASSERT_EQ(mWidth, expected.mWidth);
119         ASSERT_EQ(mHeight, expected.mHeight);
120 
121         for (int x = 0; x < mWidth; ++x)
122         {
123             for (int y = 0; y < mHeight; ++y)
124             {
125                 ASSERT_EQ(expected.mData[x + y * mWidth], mData[x + y * mWidth])
126                     << "at (" << x << ", " << y << ")";
127             }
128         }
129     }
130 
131   private:
132     int mWidth, mHeight;
133     std::vector<angle::GLColor> mData;
134 };
135 
136 }  // namespace
137 
138 namespace angle
139 {
140 
141 class WebGLReadOutsideFramebufferTest : public ANGLETest<>
142 {
143   public:
144     // Read framebuffer to 'pixelsOut' via glReadPixels.
TestReadPixels(int x,int y,int,PixelRect * pixelsOut)145     void TestReadPixels(int x, int y, int, PixelRect *pixelsOut) { pixelsOut->readFB(x, y); }
146 
147     // Read framebuffer to 'pixelsOut' via glCopyTexSubImage2D and GL_TEXTURE_2D.
TestCopyTexSubImage2D(int x,int y,int,PixelRect * pixelsOut)148     void TestCopyTexSubImage2D(int x, int y, int, PixelRect *pixelsOut)
149     {
150         // Init texture with given pixels.
151         GLTexture destTexture;
152         pixelsOut->toTexture2D(GL_TEXTURE_2D, destTexture);
153 
154         // Read framebuffer -> texture -> 'pixelsOut'
155         glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, x, y, kReadWidth, kReadHeight);
156         readTexture2D(GL_TEXTURE_2D, destTexture, kReadWidth, kReadHeight, pixelsOut);
157     }
158 
159     // Read framebuffer to 'pixelsOut' via glCopyTexSubImage2D and cube map.
TestCopyTexSubImageCube(int x,int y,int,PixelRect * pixelsOut)160     void TestCopyTexSubImageCube(int x, int y, int, PixelRect *pixelsOut)
161     {
162         // Init texture with given pixels.
163         GLTexture destTexture;
164         pixelsOut->toTexture2D(GL_TEXTURE_CUBE_MAP, destTexture);
165 
166         // Read framebuffer -> texture -> 'pixelsOut'
167         glCopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, x, y, kReadWidth, kReadHeight);
168         readTexture2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, destTexture, kReadWidth, kReadHeight,
169                       pixelsOut);
170     }
171 
172     // Read framebuffer to 'pixelsOut' via glCopyTexSubImage3D and a 2D array texture.
TestCopyTexSubImage2DArray(int x,int y,int z,PixelRect * pixelsOut)173     void TestCopyTexSubImage2DArray(int x, int y, int z, PixelRect *pixelsOut)
174     {
175         // Init texture with given pixels.
176         GLTexture destTexture;
177         pixelsOut->toTexture3D(GL_TEXTURE_2D_ARRAY, destTexture, kTextureDepth);
178 
179         // Read framebuffer -> texture -> 'pixelsOut'
180         glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, z, x, y, kReadWidth, kReadHeight);
181         readTexture3D(destTexture, kReadWidth, kReadHeight, z, pixelsOut);
182     }
183 
184     // Read framebuffer to 'pixelsOut' via glCopyTexSubImage3D.
TestCopyTexSubImage3D(int x,int y,int z,PixelRect * pixelsOut)185     void TestCopyTexSubImage3D(int x, int y, int z, PixelRect *pixelsOut)
186     {
187         // Init texture with given pixels.
188         GLTexture destTexture;
189         pixelsOut->toTexture3D(GL_TEXTURE_3D, destTexture, kTextureDepth);
190 
191         // Read framebuffer -> texture -> 'pixelsOut'
192         glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, z, x, y, kReadWidth, kReadHeight);
193         readTexture3D(destTexture, kReadWidth, kReadHeight, z, pixelsOut);
194     }
195 
196     // Read framebuffer to 'pixelsOut' via glCopyTexImage2D and GL_TEXTURE_2D.
TestCopyTexImage2D(int x,int y,int,PixelRect * pixelsOut)197     void TestCopyTexImage2D(int x, int y, int, PixelRect *pixelsOut)
198     {
199         // Init texture with given pixels.
200         GLTexture destTexture;
201         pixelsOut->toTexture2D(GL_TEXTURE_2D, destTexture);
202 
203         // Read framebuffer -> texture -> 'pixelsOut'
204         glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x, y, kReadWidth, kReadHeight, 0);
205         readTexture2D(GL_TEXTURE_2D, destTexture, kReadWidth, kReadHeight, pixelsOut);
206     }
207 
208     // Read framebuffer to 'pixelsOut' via glCopyTexImage2D and cube map.
TestCopyTexImageCube(int x,int y,int,PixelRect * pixelsOut)209     void TestCopyTexImageCube(int x, int y, int, PixelRect *pixelsOut)
210     {
211         // Init texture with given pixels.
212         GLTexture destTexture;
213         pixelsOut->toTexture2D(GL_TEXTURE_CUBE_MAP, destTexture);
214 
215         // Read framebuffer -> texture -> 'pixelsOut'
216         glCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, x, y, kReadWidth, kReadHeight,
217                          0);
218         readTexture2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, destTexture, kReadWidth, kReadHeight,
219                       pixelsOut);
220     }
221 
222   protected:
223     static constexpr int kFbWidth      = 128;
224     static constexpr int kFbHeight     = 128;
225     static constexpr int kTextureDepth = 16;
226     static constexpr int kReadWidth    = 4;
227     static constexpr int kReadHeight   = 4;
228     static constexpr int kReadLayer    = 2;
229 
230     // Tag the framebuffer pixels differently than the initial read buffer pixels, so we know for
231     // sure which pixels are changed by reading.
232     static constexpr GLuint fbTag   = 0x1122;
233     static constexpr GLuint readTag = 0xaabb;
234 
WebGLReadOutsideFramebufferTest()235     WebGLReadOutsideFramebufferTest() : mFBData(kFbWidth, kFbHeight)
236     {
237         setWindowWidth(kFbWidth);
238         setWindowHeight(kFbHeight);
239         setConfigRedBits(8);
240         setConfigGreenBits(8);
241         setConfigBlueBits(8);
242         setConfigAlphaBits(8);
243         setRobustResourceInit(true);
244         setWebGLCompatibilityEnabled(true);
245     }
246 
testSetUp()247     void testSetUp() override
248     {
249         constexpr char kVS[] = R"(
250 attribute vec3 a_position;
251 varying vec2 v_texCoord;
252 void main() {
253     v_texCoord = a_position.xy * 0.5 + 0.5;
254     gl_Position = vec4(a_position, 1);
255 })";
256         constexpr char kFS[] = R"(
257 precision mediump float;
258 varying vec2 v_texCoord;
259 uniform sampler2D u_texture;
260 void main() {
261     gl_FragColor = texture2D(u_texture, v_texCoord);
262 })";
263 
264         mProgram = CompileProgram(kVS, kFS);
265         glUseProgram(mProgram);
266         GLint uniformLoc = glGetUniformLocation(mProgram, "u_texture");
267         ASSERT_NE(-1, uniformLoc);
268         glUniform1i(uniformLoc, 0);
269 
270         glDisable(GL_BLEND);
271         glDisable(GL_DEPTH_TEST);
272 
273         // fill framebuffer with unique pixels
274         mFBData.fill(fbTag);
275         GLTexture fbTexture;
276         mFBData.toTexture2D(GL_TEXTURE_2D, fbTexture);
277         drawQuad(mProgram, "a_position", 0.0f, 1.0f, true);
278     }
279 
testTearDown()280     void testTearDown() override { glDeleteProgram(mProgram); }
281 
282     using TestFunc = void (WebGLReadOutsideFramebufferTest::*)(int x,
283                                                                int y,
284                                                                int z,
285                                                                PixelRect *dest);
286 
Main2D(TestFunc testFunc,bool zeroOutside)287     void Main2D(TestFunc testFunc, bool zeroOutside) { mainImpl(testFunc, zeroOutside, 0); }
288 
Main3D(TestFunc testFunc,bool zeroOutside)289     void Main3D(TestFunc testFunc, bool zeroOutside)
290     {
291         mainImpl(testFunc, zeroOutside, kReadLayer);
292     }
293 
mainImpl(TestFunc testFunc,bool zeroOutside,int readLayer)294     void mainImpl(TestFunc testFunc, bool zeroOutside, int readLayer)
295     {
296         PixelRect actual(kReadWidth, kReadHeight);
297         PixelRect expected(kReadWidth, kReadHeight);
298 
299         // Read a kReadWidth*kReadHeight rectangle of pixels from places that include:
300         // - completely outside framebuffer, on all sides of it (i,j < 0 or > 2)
301         // - completely inside framebuffer (i,j == 1)
302         // - straddling framebuffer boundary, at each corner and side
303         for (int i = -1; i < 4; ++i)
304         {
305             for (int j = -1; j < 4; ++j)
306             {
307                 int x = i * kFbWidth / 2 - kReadWidth / 2;
308                 int y = j * kFbHeight / 2 - kReadHeight / 2;
309 
310                 // Put unique pixel values into the read destinations.
311                 actual.fill(readTag);
312                 expected.readPixelRect(actual, 0, 0, false);
313 
314                 // Read from framebuffer into 'actual.'
315                 glBindFramebuffer(GL_FRAMEBUFFER, 0);
316                 (this->*testFunc)(x, y, readLayer, &actual);
317                 glBindFramebuffer(GL_FRAMEBUFFER, 0);
318 
319                 // Simulate framebuffer read, into 'expected.'
320                 expected.readPixelRect(mFBData, x, y, zeroOutside);
321 
322                 // See if they are the same.
323                 actual.compare(expected);
324             }
325         }
326     }
327 
328     // Get contents of given texture by drawing it into a framebuffer then reading with
329     // glReadPixels().
readTexture2D(GLuint target,GLuint texture,GLsizei width,GLsizei height,PixelRect * out)330     void readTexture2D(GLuint target, GLuint texture, GLsizei width, GLsizei height, PixelRect *out)
331     {
332         GLFramebuffer fbo;
333         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
334         glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target, texture, 0);
335         out->readFB(0, 0);
336     }
337 
338     // Get contents of current texture by drawing it into a framebuffer then reading with
339     // glReadPixels().
readTexture3D(GLuint texture,GLsizei width,GLsizei height,int zSlice,PixelRect * out)340     void readTexture3D(GLuint texture, GLsizei width, GLsizei height, int zSlice, PixelRect *out)
341     {
342         GLFramebuffer fbo;
343         glBindFramebuffer(GL_FRAMEBUFFER, fbo);
344         glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0, zSlice);
345         out->readFB(0, 0);
346     }
347 
348     PixelRect mFBData;
349     GLuint mProgram;
350 };
351 
352 class WebGL2ReadOutsideFramebufferTest : public WebGLReadOutsideFramebufferTest
353 {};
354 
355 // Check that readPixels does not set a destination pixel when
356 // the corresponding source pixel is outside the framebuffer.
TEST_P(WebGLReadOutsideFramebufferTest,ReadPixels)357 TEST_P(WebGLReadOutsideFramebufferTest, ReadPixels)
358 {
359     Main2D(&WebGLReadOutsideFramebufferTest::TestReadPixels, false);
360 }
361 
362 // Check that copyTexSubImage2D does not set a destination pixel when
363 // the corresponding source pixel is outside the framebuffer.
TEST_P(WebGLReadOutsideFramebufferTest,CopyTexSubImage2D)364 TEST_P(WebGLReadOutsideFramebufferTest, CopyTexSubImage2D)
365 {
366     Main2D(&WebGLReadOutsideFramebufferTest::TestCopyTexSubImage2D, false);
367     Main2D(&WebGLReadOutsideFramebufferTest::TestCopyTexSubImageCube, false);
368 }
369 
370 // Check that copyTexImage2D sets (0,0,0,0) for pixels outside the framebuffer.
TEST_P(WebGLReadOutsideFramebufferTest,CopyTexImage2D)371 TEST_P(WebGLReadOutsideFramebufferTest, CopyTexImage2D)
372 {
373     Main2D(&WebGLReadOutsideFramebufferTest::TestCopyTexImage2D, true);
374     Main2D(&WebGLReadOutsideFramebufferTest::TestCopyTexImageCube, true);
375 }
376 
377 // Check that copyTexSubImage3D does not set a destination pixel when
378 // the corresponding source pixel is outside the framebuffer.
TEST_P(WebGL2ReadOutsideFramebufferTest,CopyTexSubImage3D)379 TEST_P(WebGL2ReadOutsideFramebufferTest, CopyTexSubImage3D)
380 {
381     Main3D(&WebGLReadOutsideFramebufferTest::TestCopyTexSubImage2DArray, false);
382     Main3D(&WebGLReadOutsideFramebufferTest::TestCopyTexSubImage3D, false);
383 }
384 
385 ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(WebGLReadOutsideFramebufferTest);
386 
387 ANGLE_INSTANTIATE_TEST_ES3(WebGL2ReadOutsideFramebufferTest);
388 
389 }  // namespace angle
390