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