| // | |
| // Copyright 2015 The ANGLE Project Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // | |
| #include <array> | |
| #include "common/unsafe_buffers.h" | |
| #include "test_utils/ANGLETest.h" | |
| #include "test_utils/gl_raii.h" | |
| #include "util/EGLWindow.h" | |
| #include "util/random_utils.h" | |
| #include "util/test_utils.h" | |
| using namespace angle; | |
| class OcclusionQueriesTest : public ANGLETest<> | |
| { | |
| protected: | |
| OcclusionQueriesTest() : mProgram(0), mRNG(1) | |
| { | |
| setWindowWidth(128); | |
| setWindowHeight(128); | |
| setConfigRedBits(8); | |
| setConfigGreenBits(8); | |
| setConfigBlueBits(8); | |
| setConfigAlphaBits(8); | |
| setConfigDepthBits(24); | |
| } | |
| void testSetUp() override | |
| { | |
| mProgram = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); | |
| ASSERT_NE(0u, mProgram); | |
| } | |
| void testTearDown() override { glDeleteProgram(mProgram); } | |
| GLuint mProgram; | |
| RNG mRNG; | |
| }; | |
| class OcclusionQueriesTestES3 : public OcclusionQueriesTest | |
| {}; | |
| TEST_P(OcclusionQueriesTest, IsOccluded) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| glDepthMask(GL_TRUE); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| // draw a quad at depth 0.3 | |
| glEnable(GL_DEPTH_TEST); | |
| glUseProgram(mProgram); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.3f); | |
| glUseProgram(0); | |
| EXPECT_GL_NO_ERROR(); | |
| GLQueryEXT query; | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), | |
| 0.8f); // this quad should be occluded by first quad | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| swapBuffers(); | |
| GLuint ready = GL_FALSE; | |
| while (ready == GL_FALSE) | |
| { | |
| angle::Sleep(0); | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_AVAILABLE_EXT, &ready); | |
| } | |
| GLuint result = GL_TRUE; | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, &result); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_FALSE(result); | |
| } | |
| TEST_P(OcclusionQueriesTest, IsNotOccluded) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| glDepthMask(GL_TRUE); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| EXPECT_GL_NO_ERROR(); | |
| GLQueryEXT query; | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f); // this quad should not be occluded | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| swapBuffers(); | |
| GLuint result = GL_TRUE; | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, &result); // will block waiting for result | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_TRUE(result); | |
| } | |
| // Test that glClear should not be counted by occlusion query. | |
| TEST_P(OcclusionQueriesTest, ClearNotCounted) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| // http://anglebug.com/42263499 | |
| ANGLE_SKIP_TEST_IF(IsD3D11()); | |
| glDepthMask(GL_TRUE); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| EXPECT_GL_NO_ERROR(); | |
| GLQueryEXT query[2]; | |
| // First query | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[0]); | |
| // Full screen clear | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| // View port clear | |
| glViewport(0, 0, getWindowWidth() / 2, getWindowHeight()); | |
| glScissor(0, 0, getWindowWidth() / 2, getWindowHeight()); | |
| glEnable(GL_SCISSOR_TEST); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| // Second query | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[1]); | |
| // View port clear | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| // View port clear | |
| glViewport(0, 0, getWindowWidth() / 2, getWindowHeight()); | |
| glScissor(0, 0, getWindowWidth() / 2, getWindowHeight()); | |
| glEnable(GL_SCISSOR_TEST); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| // this quad should not be occluded | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f, 0.5f); | |
| // Clear again | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| // this quad should not be occluded | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f, 1.0); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| swapBuffers(); | |
| GLuint result[2] = {GL_TRUE, GL_TRUE}; | |
| glGetQueryObjectuivEXT(query[0], GL_QUERY_RESULT_EXT, | |
| &result[0]); // will block waiting for result | |
| glGetQueryObjectuivEXT(query[1], GL_QUERY_RESULT_EXT, | |
| &result[1]); // will block waiting for result | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_FALSE(result[0]); | |
| EXPECT_GL_TRUE(result[1]); | |
| } | |
| // Test that masked glClear should not be counted by occlusion query. | |
| TEST_P(OcclusionQueriesTest, MaskedClearNotCounted) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| // http://anglebug.com/42263499 | |
| ANGLE_SKIP_TEST_IF(IsD3D()); | |
| GLQueryEXT query; | |
| // Masked clear | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| glColorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_TRUE); | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| swapBuffers(); | |
| GLuint result = GL_TRUE; | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, | |
| &result); // will block waiting for result | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_FALSE(result); | |
| } | |
| // Test that copies should not be counted by occlusion query. | |
| TEST_P(OcclusionQueriesTest, CopyNotCounted) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| // http://anglebug.com/42263499 | |
| ANGLE_SKIP_TEST_IF(IsD3D()); | |
| GLQueryEXT query; | |
| // Unrelated draw before the query starts. | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f, 0.5f); | |
| // Copy to a texture with a different format from backbuffer | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| GLTexture tex; | |
| glBindTexture(GL_TEXTURE_2D, tex); | |
| glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, getWindowWidth(), getWindowHeight(), 0); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| swapBuffers(); | |
| GLuint result = GL_TRUE; | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, | |
| &result); // will block waiting for result | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_FALSE(result); | |
| } | |
| // Test that blit should not be counted by occlusion query. | |
| TEST_P(OcclusionQueriesTestES3, BlitNotCounted) | |
| { | |
| // http://anglebug.com/42263499 | |
| ANGLE_SKIP_TEST_IF(IsD3D11()); | |
| // http://anglebug.com/42263669 | |
| ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan()); | |
| constexpr GLuint kSize = 64; | |
| GLFramebuffer srcFbo; | |
| glBindFramebuffer(GL_FRAMEBUFFER, srcFbo); | |
| GLTexture srcTex; | |
| glBindTexture(GL_TEXTURE_2D, srcTex); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); | |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, srcTex, 0); | |
| GLFramebuffer dstFbo; | |
| glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo); | |
| GLTexture dstTex; | |
| glBindTexture(GL_TEXTURE_2D, dstTex); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, kSize, kSize, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); | |
| glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dstTex, 0); | |
| GLQueryEXT query; | |
| // Unrelated draw before the query starts. | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f, 0.5f); | |
| // Blit flipped and with different formats. | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| glBlitFramebuffer(0, 0, 64, 64, 64, 64, 0, 0, GL_COLOR_BUFFER_BIT, GL_LINEAR); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| swapBuffers(); | |
| GLuint result = GL_TRUE; | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, | |
| &result); // will block waiting for result | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_FALSE(result); | |
| } | |
| // Test that multisampled-render-to-texture unresolve should not be counted by occlusion query. | |
| TEST_P(OcclusionQueriesTestES3, UnresolveNotCounted) | |
| { | |
| ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture")); | |
| constexpr GLuint kSize = 64; | |
| GLFramebuffer fboMS; | |
| glBindFramebuffer(GL_FRAMEBUFFER, fboMS); | |
| // Create multisampled framebuffer to draw into | |
| GLTexture textureMS; | |
| glBindTexture(GL_TEXTURE_2D, textureMS); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); | |
| glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, | |
| textureMS, 0, 4); | |
| GLRenderbuffer depthMS; | |
| glBindRenderbuffer(GL_RENDERBUFFER, depthMS); | |
| glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, kSize, kSize); | |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthMS); | |
| EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); | |
| // Draw red into the multisampled color buffer. | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); | |
| ASSERT_GL_NO_ERROR(); | |
| // Create a texture and copy into it, forcing a resolve of the color buffer. | |
| GLTexture texture; | |
| glBindTexture(GL_TEXTURE_2D, texture); | |
| glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, kSize, kSize, 0); | |
| GLQueryEXT query; | |
| // Make a draw call that will fail the depth test, and therefore shouldn't contribute to | |
| // occlusion query. | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| glEnable(GL_DEPTH_TEST); | |
| glDepthFunc(GL_NEVER); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f, 0.5f); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| swapBuffers(); | |
| GLuint result = GL_TRUE; | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, | |
| &result); // will block waiting for result | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_FALSE(result); | |
| } | |
| // Test that reusing a query should reset its value to zero if no draw calls are emitted in the | |
| // second pass. | |
| TEST_P(OcclusionQueriesTest, RewriteDrawNoDrawToZero) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| GLQueryEXT query; | |
| glDepthMask(GL_TRUE); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| // draw a quad at depth 0.3 | |
| glEnable(GL_DEPTH_TEST); | |
| glUseProgram(mProgram); | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.3f); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| glUseProgram(0); | |
| EXPECT_GL_NO_ERROR(); | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| swapBuffers(); | |
| GLuint ready = GL_FALSE; | |
| while (ready == GL_FALSE) | |
| { | |
| angle::Sleep(0); | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_AVAILABLE_EXT, &ready); | |
| } | |
| GLuint result = GL_TRUE; | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, &result); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_FALSE(result); | |
| } | |
| // Test that changing framebuffers work | |
| TEST_P(OcclusionQueriesTest, FramebufferBindingChange) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| constexpr GLsizei kSize = 4; | |
| // Create two framebuffers, and make sure they are synced. | |
| std::array<GLFramebuffer, 2> fbo; | |
| std::array<GLTexture, 2> color; | |
| for (size_t index = 0; index < 2; ++index) | |
| { | |
| glBindTexture(GL_TEXTURE_2D, color[index]); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, | |
| nullptr); | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo[index]); | |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color[index], | |
| 0); | |
| glClearColor(0, index, 1 - index, 1); | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| EXPECT_PIXEL_COLOR_EQ(0, 0, index ? GLColor::green : GLColor::blue); | |
| } | |
| EXPECT_GL_NO_ERROR(); | |
| glViewport(0, 0, kSize, kSize); | |
| // Start an occlusion query and issue a draw call to each framebuffer. | |
| GLQueryEXT query; | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| for (size_t index = 0; index < 2; ++index) | |
| { | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo[index]); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); | |
| } | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| GLuint result = GL_FALSE; | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, &result); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_TRUE(result); | |
| } | |
| // Test that switching framebuffers without actually drawing, then issuing a masked clear while a | |
| // query is active works. | |
| TEST_P(OcclusionQueriesTestES3, SwitchFramebuffersThenMaskedClear) | |
| { | |
| constexpr GLint kSize = 10; | |
| GLFramebuffer fbo1, fbo2; | |
| GLRenderbuffer rbo1, rbo2; | |
| // Set up two framebuffers | |
| glBindRenderbuffer(GL_RENDERBUFFER, rbo1); | |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, kSize, kSize); | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo1); | |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo1); | |
| glBindRenderbuffer(GL_RENDERBUFFER, rbo2); | |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, kSize, kSize); | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo2); | |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo2); | |
| // Start render pass on fbo1 | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo1); | |
| ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green()); | |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0); | |
| // Begin a query | |
| GLQuery query; | |
| glBeginQuery(GL_ANY_SAMPLES_PASSED, query); | |
| // Switch to another render pass and clear. In the Vulkan backend, this clear is deferred, so | |
| // while the framebuffer binding is synced, the previous render pass is not necessarily closed. | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo2); | |
| glClear(GL_STENCIL_BUFFER_BIT); | |
| // Switch back to the original render pass and issue a masked stencil clear. In the Vulkan | |
| // backend, this is done with a draw call. | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo1); | |
| glStencilMask(0xAA); | |
| glClearStencil(0xF4); | |
| glClear(GL_STENCIL_BUFFER_BIT); | |
| // Verify the clear worked. | |
| GLRenderbuffer color; | |
| glBindRenderbuffer(GL_RENDERBUFFER, color); | |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSize, kSize); | |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color); | |
| glEnable(GL_STENCIL_TEST); | |
| glStencilFunc(GL_ALWAYS, 0xA4, 0xFF); | |
| glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); | |
| glStencilMask(0xFF); | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0); | |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Test that an empty query after a positive query returns false | |
| TEST_P(OcclusionQueriesTest, EmptyQueryAfterCompletedQuery) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| GLQueryEXT query; | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| ASSERT_GL_NO_ERROR(); | |
| GLuint result = GL_FALSE; | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, &result); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_TRUE(result); | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| ASSERT_GL_NO_ERROR(); | |
| result = GL_FALSE; | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, &result); | |
| ASSERT_GL_NO_ERROR(); | |
| EXPECT_FALSE(result); | |
| } | |
| // Some Metal drivers do not automatically clear visibility buffer | |
| // at the beginning of a render pass. This test makes two queries | |
| // that would use the same internal visibility buffer at the same | |
| // offset and checks the query results. | |
| TEST_P(OcclusionQueriesTest, EmptyQueryAfterCompletedQueryInterleaved) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| GLQueryEXT query; | |
| // Make a draw call to start a new render pass | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0f); | |
| // Begin a query and make another draw call | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0f); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| // Check the query result to end command encoding | |
| GLuint result = GL_FALSE; | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, &result); | |
| EXPECT_TRUE(result); | |
| ASSERT_GL_NO_ERROR(); | |
| // Make a draw call to start a new render pass | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0f); | |
| // Begin and immediately resolve a new query; it must return false | |
| result = GL_FALSE; | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, &result); | |
| EXPECT_FALSE(result); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| // Test multiple occlusion queries. | |
| TEST_P(OcclusionQueriesTest, MultiQueries) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| // http://anglebug.com/42263499 | |
| ANGLE_SKIP_TEST_IF(IsOpenGL() || IsD3D11()); | |
| GLQueryEXT query[5]; | |
| // First query | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[0]); | |
| EXPECT_GL_NO_ERROR(); | |
| glEnable(GL_DEPTH_TEST); | |
| glDepthMask(GL_TRUE); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| EXPECT_GL_NO_ERROR(); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f); // this quad should not be occluded | |
| EXPECT_GL_NO_ERROR(); | |
| // Due to implementation might skip in-renderpass flush, we are using glFinish here to force a | |
| // flush. A flush shound't clear the query result. | |
| glFinish(); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), -2, 0.25f); // this quad should be occluded | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| // First query ends | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f, | |
| 0.25f); // this quad should not be occluded | |
| // Second query | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[1]); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.9f, | |
| 0.25f); // this quad should be occluded | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| // Third query | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[2]); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.9f, | |
| 0.5f); // this quad should not be occluded | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| // ------------ | |
| glFinish(); | |
| glViewport(0, 0, getWindowWidth() / 2, getWindowHeight()); | |
| glScissor(0, 0, getWindowWidth() / 2, getWindowHeight()); | |
| glEnable(GL_SCISSOR_TEST); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.9f, | |
| 0.5f); // this quad should not be occluded | |
| // Fourth query: begin query then end then begin again | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[3]); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.9f, | |
| 1); // this quad should not be occluded | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[3]); | |
| EXPECT_GL_NO_ERROR(); | |
| // glClear should not be counted toward query); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| // Fifth query spans across frames | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query[4]); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f, | |
| 0.25f); // this quad should not be occluded | |
| swapBuffers(); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.9f, | |
| 0.5f); // this quad should not be occluded | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| GLuint result = GL_TRUE; | |
| glGetQueryObjectuivEXT(query[0], GL_QUERY_RESULT_EXT, | |
| &result); // will block waiting for result | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_TRUE(result); | |
| glGetQueryObjectuivEXT(query[1], GL_QUERY_RESULT_EXT, | |
| &result); // will block waiting for result | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_FALSE(result); | |
| glGetQueryObjectuivEXT(query[2], GL_QUERY_RESULT_EXT, | |
| &result); // will block waiting for result | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_TRUE(result); | |
| glGetQueryObjectuivEXT(query[3], GL_QUERY_RESULT_EXT, | |
| &result); // will block waiting for result | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_FALSE(result); | |
| glGetQueryObjectuivEXT(query[4], GL_QUERY_RESULT_EXT, | |
| &result); // will block waiting for result | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_TRUE(result); | |
| } | |
| TEST_P(OcclusionQueriesTest, Errors) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| glDepthMask(GL_TRUE); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| EXPECT_GL_NO_ERROR(); | |
| GLuint query = 0; | |
| GLuint query2 = 0; | |
| glGenQueriesEXT(1, &query); | |
| EXPECT_GL_FALSE(glIsQueryEXT(query)); | |
| EXPECT_GL_FALSE(glIsQueryEXT(query2)); | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, 0); // can't pass 0 as query id | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT, | |
| query2); // can't initiate a query while one's already active | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| EXPECT_GL_TRUE(glIsQueryEXT(query)); | |
| EXPECT_GL_FALSE(glIsQueryEXT(query2)); // have not called begin | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f); // this quad should not be occluded | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT); // no active query for this target | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT, | |
| query); // can't begin a query as a different type than previously used | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT, | |
| query2); // have to call genqueries first | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| glGenQueriesEXT(1, &query2); | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT, query2); // should be ok now | |
| EXPECT_GL_TRUE(glIsQueryEXT(query2)); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), | |
| 0.3f); // this should draw in front of other quad | |
| glDeleteQueriesEXT(1, &query2); // should delete when query becomes inactive | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_CONSERVATIVE_EXT); // should not incur error; should delete | |
| // query + 1 at end of execution. | |
| EXPECT_GL_NO_ERROR(); | |
| swapBuffers(); | |
| EXPECT_GL_NO_ERROR(); | |
| GLuint ready = GL_FALSE; | |
| glGetQueryObjectuivEXT(query2, GL_QUERY_RESULT_AVAILABLE_EXT, | |
| &ready); // this query is now deleted | |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| // Test that running multiple simultaneous queries from multiple EGL contexts returns the correct | |
| // result for each query. Helps expose bugs in ANGLE's virtual contexts. | |
| TEST_P(OcclusionQueriesTest, MultiContext) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| // TODO(cwallez@chromium.org): Suppression for http://anglebug.com/42261759 | |
| ANGLE_SKIP_TEST_IF(IsWindows() && IsNVIDIA() && IsVulkan()); | |
| // Test skipped because the D3D backends cannot support simultaneous queries on multiple | |
| // contexts yet. | |
| ANGLE_SKIP_TEST_IF(IsD3D()); | |
| glDepthMask(GL_TRUE); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| // draw a quad at depth 0.5 | |
| glEnable(GL_DEPTH_TEST); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); | |
| EGLWindow *window = getEGLWindow(); | |
| EGLDisplay display = window->getDisplay(); | |
| EGLConfig config = window->getConfig(); | |
| EGLSurface surface = window->getSurface(); | |
| EGLint contextAttributes[] = { | |
| EGL_CONTEXT_MAJOR_VERSION_KHR, | |
| getClientMajorVersion(), | |
| EGL_CONTEXT_MINOR_VERSION_KHR, | |
| getClientMinorVersion(), | |
| EGL_NONE, | |
| }; | |
| const size_t passCount = 5; | |
| struct ContextInfo | |
| { | |
| EGLContext context; | |
| GLuint program; | |
| GLuint query; | |
| std::array<bool, passCount> visiblePasses; | |
| bool shouldPass; | |
| }; | |
| ContextInfo contexts[] = { | |
| { | |
| EGL_NO_CONTEXT, | |
| 0, | |
| 0, | |
| {false, false, false, false, false}, | |
| false, | |
| }, | |
| { | |
| EGL_NO_CONTEXT, | |
| 0, | |
| 0, | |
| {false, true, false, true, false}, | |
| true, | |
| }, | |
| { | |
| EGL_NO_CONTEXT, | |
| 0, | |
| 0, | |
| {false, false, false, false, false}, | |
| false, | |
| }, | |
| { | |
| EGL_NO_CONTEXT, | |
| 0, | |
| 0, | |
| {true, true, false, true, true}, | |
| true, | |
| }, | |
| { | |
| EGL_NO_CONTEXT, | |
| 0, | |
| 0, | |
| {false, true, true, true, true}, | |
| true, | |
| }, | |
| { | |
| EGL_NO_CONTEXT, | |
| 0, | |
| 0, | |
| {true, false, false, true, false}, | |
| true, | |
| }, | |
| { | |
| EGL_NO_CONTEXT, | |
| 0, | |
| 0, | |
| {false, false, false, false, false}, | |
| false, | |
| }, | |
| { | |
| EGL_NO_CONTEXT, | |
| 0, | |
| 0, | |
| {false, false, false, false, false}, | |
| false, | |
| }, | |
| }; | |
| for (auto &context : contexts) | |
| { | |
| context.context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttributes); | |
| ASSERT_NE(context.context, EGL_NO_CONTEXT); | |
| eglMakeCurrent(display, surface, surface, context.context); | |
| context.program = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); | |
| ASSERT_NE(context.program, 0u); | |
| glDepthMask(GL_FALSE); | |
| glEnable(GL_DEPTH_TEST); | |
| glGenQueriesEXT(1, &context.query); | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, context.query); | |
| ASSERT_GL_NO_ERROR(); | |
| } | |
| for (size_t pass = 0; pass < passCount; pass++) | |
| { | |
| for (const auto &context : contexts) | |
| { | |
| eglMakeCurrent(display, surface, surface, context.context); | |
| float depth = context.visiblePasses[pass] ? mRNG.randomFloatBetween(0.0f, 0.4f) | |
| : mRNG.randomFloatBetween(0.6f, 1.0f); | |
| drawQuad(context.program, essl1_shaders::PositionAttrib(), depth); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| } | |
| for (const auto &context : contexts) | |
| { | |
| eglMakeCurrent(display, surface, surface, context.context); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| GLuint result = GL_TRUE; | |
| glGetQueryObjectuivEXT(context.query, GL_QUERY_RESULT_EXT, &result); | |
| EXPECT_GL_NO_ERROR(); | |
| GLuint expectation = context.shouldPass ? GL_TRUE : GL_FALSE; | |
| EXPECT_EQ(expectation, result); | |
| } | |
| eglMakeCurrent(display, surface, surface, window->getContext()); | |
| for (auto &context : contexts) | |
| { | |
| eglDestroyContext(display, context.context); | |
| context.context = EGL_NO_CONTEXT; | |
| } | |
| } | |
| // Test multiple occlusion queries in flight. This test provoked a bug in the Metal backend that | |
| // resulted in an infinite loop when trying to flush the command buffer when the maximum number of | |
| // inflight render passes was reached. | |
| TEST_P(OcclusionQueriesTest, ManyQueriesInFlight) | |
| { | |
| constexpr int kManyQueryCount = 100; | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| // http://anglebug.com/42263499 | |
| ANGLE_SKIP_TEST_IF(IsOpenGL() || IsD3D11()); | |
| GLQueryEXT query; | |
| glEnable(GL_DEPTH_TEST); | |
| glDepthMask(GL_TRUE); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| GLRenderbuffer rbo[2]; | |
| glBindRenderbuffer(GL_RENDERBUFFER, rbo[0]); | |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 32, 32); | |
| glBindRenderbuffer(GL_RENDERBUFFER, rbo[1]); | |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 32, 32); | |
| GLFramebuffer fbo; | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo[0]); | |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo[1]); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| EXPECT_GL_NO_ERROR(); | |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); | |
| for (int i = 0; i < kManyQueryCount; i++) | |
| { | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 1.0f - 2.0f * i / kManyQueryCount); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f); | |
| } | |
| glFinish(); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| // Test two occlusion queries in sequence and there are some glBindFramebuffer in between. | |
| // This test provoked a bug that the second query been skipped. | |
| TEST_P(OcclusionQueriesTest, WrongSkippedQuery) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| GLRenderbuffer rbo; | |
| glBindRenderbuffer(GL_RENDERBUFFER, rbo); | |
| glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 32, 32); | |
| GLFramebuffer fbo; | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo); | |
| EXPECT_GL_NO_ERROR(); | |
| GLQueryEXT query1; | |
| // Draw square in 1st FBO, clear main framebuffer - main framebuffer is active after | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query1); | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); | |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); | |
| glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| GLQueryEXT query2; | |
| // Draw square in FBO, clear main framebuffer - FBO is active after | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query2); | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.5f); | |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); | |
| glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| GLuint results[2] = {0}; | |
| GLuint expectation = GL_TRUE; | |
| glGetQueryObjectuivEXT(query1, GL_QUERY_RESULT_EXT, &results[0]); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_EQ(expectation, results[0]); | |
| glGetQueryObjectuivEXT(query2, GL_QUERY_RESULT_EXT, &results[1]); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_EQ(expectation, results[1]); | |
| } | |
| class OcclusionQueriesNoSurfaceTestES3 : public ANGLETest<> | |
| { | |
| protected: | |
| OcclusionQueriesNoSurfaceTestES3() : mUnusedConfig(0), mUnusedDisplay(nullptr) | |
| { | |
| setWindowWidth(kWidth); | |
| setWindowHeight(kHeight); | |
| setConfigRedBits(8); | |
| setConfigGreenBits(8); | |
| setConfigBlueBits(8); | |
| setConfigAlphaBits(8); | |
| setDeferContextInit(true); | |
| } | |
| static constexpr int kWidth = 300; | |
| static constexpr int kHeight = 300; | |
| void swapBuffers() override {} | |
| EGLConfig mUnusedConfig; | |
| EGLDisplay mUnusedDisplay; | |
| }; | |
| // This test provked a bug in the Metal backend that only happened | |
| // when there was no surfaces on the EGLContext and a query had | |
| // just ended after a draw and then switching to a different | |
| // context. | |
| TEST_P(OcclusionQueriesNoSurfaceTestES3, SwitchingContextsWithQuery) | |
| { | |
| EGLWindow *window = getEGLWindow(); | |
| EGLDisplay display = window->getDisplay(); | |
| EGLConfig config = window->getConfig(); | |
| EGLint contextAttributes[] = { | |
| EGL_CONTEXT_MAJOR_VERSION_KHR, | |
| getClientMajorVersion(), | |
| EGL_CONTEXT_MINOR_VERSION_KHR, | |
| getClientMinorVersion(), | |
| EGL_ROBUST_RESOURCE_INITIALIZATION_ANGLE, | |
| EGL_TRUE, | |
| EGL_NONE, | |
| }; | |
| // The following GL objects are implicitly deleted in | |
| // ContextInfo's destructor before the EGLContext is manually destroyed | |
| struct ContextInfo | |
| { | |
| EGLContext context; | |
| GLBuffer buf; | |
| GLProgram program; | |
| GLFramebuffer fb; | |
| GLTexture tex; | |
| GLQuery query; | |
| }; | |
| // ContextInfo contains objects that clean themselves on destruction. | |
| // We want these objects to stick around until the test ends. | |
| std::vector<ContextInfo *> pairs; | |
| for (size_t i = 0; i < 2; ++i) | |
| { | |
| ContextInfo *infos[] = { | |
| new ContextInfo(), | |
| new ContextInfo(), | |
| }; | |
| for (ContextInfo *pinfo : infos) | |
| { | |
| pairs.push_back(pinfo); | |
| ContextInfo &info = *pinfo; | |
| info.context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttributes); | |
| ASSERT_NE(info.context, EGL_NO_CONTEXT); | |
| // Make context current context with no draw and read surface. | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, info.context)); | |
| // Create something to draw to. | |
| glBindFramebuffer(GL_FRAMEBUFFER, info.fb); | |
| glBindTexture(GL_TEXTURE_2D, info.tex); | |
| glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1); | |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, info.tex, | |
| 0); | |
| EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| EXPECT_GL_NO_ERROR(); | |
| glFlush(); | |
| } | |
| // Setup an shader and quad buffer | |
| for (ContextInfo *pinfo : infos) | |
| { | |
| ContextInfo &info = *pinfo; | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, info.context)); | |
| constexpr char kVS[] = R"( | |
| attribute vec4 position; | |
| void main() { | |
| gl_Position = position; | |
| } | |
| )"; | |
| constexpr char kFS[] = R"( | |
| precision mediump float; | |
| void main() { | |
| gl_FragColor = vec4(1, 0, 0, 1); | |
| } | |
| )"; | |
| info.program.makeRaster(kVS, kFS); | |
| glUseProgram(info.program); | |
| constexpr float vertices[] = { | |
| -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, | |
| }; | |
| glBindBuffer(GL_ARRAY_BUFFER, info.buf); | |
| glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); | |
| glEnableVertexAttribArray(0); | |
| glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| ContextInfo &info1 = *infos[0]; | |
| ContextInfo &info2 = *infos[1]; | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, info1.context)); | |
| glBeginQuery(GL_ANY_SAMPLES_PASSED_CONSERVATIVE, info1.query); | |
| glFlush(); | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, info2.context)); | |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, info1.context)); | |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | |
| glEndQuery(GL_ANY_SAMPLES_PASSED_CONSERVATIVE); | |
| EXPECT_GL_NO_ERROR(); | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, info2.context)); | |
| glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, info1.context)); | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, info2.context)); | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, info1.context)); | |
| } | |
| // destroy GL objects on the correct context. | |
| for (ContextInfo *pinfo : pairs) | |
| { | |
| EGLContext context = pinfo->context; | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, nullptr, nullptr, context)); | |
| EXPECT_GL_NO_ERROR(); | |
| delete pinfo; | |
| ASSERT_EGL_TRUE(eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); | |
| ASSERT_EGL_TRUE(eglDestroyContext(display, context)); | |
| EXPECT_EGL_SUCCESS(); | |
| } | |
| } | |
| // This test provoked a bug in the Metal backend where a command buffer flush during query | |
| // preparation would recursively clear mAllocatedQueries, causing fillBuffer to be called with | |
| // size 0, resulting in a Metal validation error (when run with MTL_DEBUG_LAYER=1). | |
| TEST_P(OcclusionQueriesTestES3, TextureResizeWithQueryReuse) | |
| { | |
| // The Metal backend flushes command buffers after this many render passes | |
| // (defined in src/libANGLE/renderer/metal/mtl_common.h) | |
| constexpr int kMaxRenderPassesPerCommandBuffer = 16; | |
| GLQueryEXT query; | |
| GLTexture texture; | |
| glBindTexture(GL_TEXTURE_2D, texture); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 2, 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr); | |
| EXPECT_GL_NO_ERROR(); | |
| glBeginQuery(GL_ANY_SAMPLES_PASSED, query); | |
| glEndQuery(GL_ANY_SAMPLES_PASSED); | |
| EXPECT_GL_NO_ERROR(); | |
| // Call texImage2D enough times to accumulate render passes up to the flush limit. | |
| const int numTexImageCalls = kMaxRenderPassesPerCommandBuffer - 1; | |
| for (int i = 0; i < numTexImageCalls; i++) | |
| { | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 2, 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, | |
| nullptr); | |
| EXPECT_GL_NO_ERROR(); | |
| } | |
| // Begin query again with the SAME query object. | |
| glBeginQuery(GL_ANY_SAMPLES_PASSED, query); | |
| EXPECT_GL_NO_ERROR(); | |
| // This texImage2D is the 16th render pass, triggering a command buffer flush. | |
| // Without a fix, prepareRenderPassVisibilityPoolBuffer would call fillBuffer with size 0 | |
| // because mAllocatedQueries gets cleared during the recursive flush. | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 2, 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr); | |
| EXPECT_GL_NO_ERROR(); | |
| glEndQuery(GL_ANY_SAMPLES_PASSED); | |
| EXPECT_GL_NO_ERROR(); | |
| GLuint result = GL_TRUE; | |
| glGetQueryObjectuiv(query, GL_QUERY_RESULT, &result); | |
| EXPECT_GL_NO_ERROR(); | |
| // Result should be false since no draw calls were made. | |
| EXPECT_GL_FALSE(result); | |
| } | |
| // Test query reuse with render pass accumulation using draw calls to distinct FBOs. | |
| TEST_P(OcclusionQueriesTestES3, QueryReuseWithMultipleFBOs) | |
| { | |
| ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); | |
| constexpr int kNumRenderPasses = 16; | |
| std::vector<GLTexture> textures(kNumRenderPasses); | |
| std::vector<GLFramebuffer> fbos(kNumRenderPasses); | |
| for (int i = 0; i < kNumRenderPasses; i++) | |
| { | |
| glBindTexture(GL_TEXTURE_2D, textures[i]); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbos[i]); | |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[i], 0); | |
| ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); | |
| } | |
| GLQueryEXT query; | |
| glBeginQuery(GL_ANY_SAMPLES_PASSED, query); | |
| glEndQuery(GL_ANY_SAMPLES_PASSED); | |
| for (int i = 0; i < kNumRenderPasses - 1; i++) | |
| { | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbos[i]); | |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f); | |
| } | |
| glBeginQuery(GL_ANY_SAMPLES_PASSED, query); | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbos[kNumRenderPasses - 1]); | |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f); | |
| glEndQuery(GL_ANY_SAMPLES_PASSED); | |
| GLuint result = GL_FALSE; | |
| glGetQueryObjectuiv(query, GL_QUERY_RESULT, &result); | |
| EXPECT_GL_TRUE(result); | |
| } | |
| // Test that deleting an FBO while a query result on that FBO is pending does not crash the driver | |
| // when calling glGetQueryObjectuiv. | |
| TEST_P(OcclusionQueriesTestES3, DeleteFBOWithPendingQuery) | |
| { | |
| ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); | |
| GLTexture texture; | |
| glBindTexture(GL_TEXTURE_2D, texture); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); | |
| GLFramebuffer fbo; | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); | |
| ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); | |
| glViewport(0, 0, 64, 64); | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| GLQueryEXT query; | |
| glBeginQuery(GL_ANY_SAMPLES_PASSED, query); | |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f); | |
| glEndQuery(GL_ANY_SAMPLES_PASSED); | |
| // Delete the FBO on which the query was executed, while result is still pending | |
| fbo.reset(); | |
| // Retrieve query result - flushes pending jobs referencing the deleted FBO | |
| GLuint result = GL_FALSE; | |
| glGetQueryObjectuiv(query, GL_QUERY_RESULT, &result); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_TRUE(result); | |
| } | |
| // Test that unbinding an FBO while a query result on that FBO is pending does not crash the driver | |
| // when calling glGetQueryObjectuiv. | |
| TEST_P(OcclusionQueriesTestES3, UnbindFBOWithPendingQuery) | |
| { | |
| ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); | |
| GLTexture texture; | |
| glBindTexture(GL_TEXTURE_2D, texture); | |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); | |
| GLFramebuffer fbo; | |
| glBindFramebuffer(GL_FRAMEBUFFER, fbo); | |
| glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); | |
| ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); | |
| glViewport(0, 0, 64, 64); | |
| glClearColor(0.0f, 0.0f, 0.0f, 1.0f); | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| GLQueryEXT query; | |
| glBeginQuery(GL_ANY_SAMPLES_PASSED, query); | |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f); | |
| glEndQuery(GL_ANY_SAMPLES_PASSED); | |
| // Unbind user FBO by switching back to default framebuffer (0) | |
| glBindFramebuffer(GL_FRAMEBUFFER, 0); | |
| // FORCE the driver to commit the unbind and end the previous render pass. | |
| // Because drivers often optimize glClear, issuing geometry (drawQuad) | |
| // strictly guarantees a new render pass is initiated in the hardware. | |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f); | |
| // Retrieve query result | |
| GLuint result = GL_FALSE; | |
| glGetQueryObjectuiv(query, GL_QUERY_RESULT, &result); | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_TRUE(result); | |
| } | |
| // Test that a very large number of scissored clears while an occlusion query is active still | |
| // yields a correct query result. On backends that emulate scissored clears with draws, each clear | |
| // pauses and resumes the query in the same render pass; the render pass must be split before the | |
| // backend's per-pass visibility offset limit is reached. | |
| TEST_P(OcclusionQueriesTest, ManyScissoredClearsInsideQuery) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| // Metal allows at most 32768 visibility result slots per render pass. | |
| constexpr int kClearCount = 35000; | |
| glDepthMask(GL_TRUE); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |
| EXPECT_GL_NO_ERROR(); | |
| GLQueryEXT query; | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| // this quad should not be occluded | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f); | |
| glEnable(GL_SCISSOR_TEST); | |
| glScissor(0, 0, 1, 1); | |
| for (int i = 0; i < kClearCount; ++i) | |
| { | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| } | |
| glDisable(GL_SCISSOR_TEST); | |
| // this quad should not be occluded | |
| drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.8f); | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| EXPECT_GL_NO_ERROR(); | |
| GLuint result = GL_FALSE; | |
| glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, | |
| &result); // will block waiting for result | |
| EXPECT_GL_NO_ERROR(); | |
| EXPECT_GL_TRUE(result); | |
| } | |
| // Test that a very large number of distinct queries and draw calls in a single render pass | |
| // still works and yields correct query results when exceeding the backend's per-pass | |
| // visibility offset limit. | |
| TEST_P(OcclusionQueriesTest, ExcessiveNumberOfQueries) | |
| { | |
| ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 && | |
| !IsGLExtensionEnabled("GL_EXT_occlusion_query_boolean")); | |
| constexpr int kQueryCount = 35000; | |
| glDisable(GL_DEPTH_TEST); | |
| glClear(GL_COLOR_BUFFER_BIT); | |
| EXPECT_GL_NO_ERROR(); | |
| constexpr char kVS[] = | |
| "attribute vec4 a_position;\n" | |
| "attribute vec4 a_color;\n" | |
| "varying vec4 v_color;\n" | |
| "void main() {\n" | |
| " gl_Position = a_position;\n" | |
| " gl_PointSize = 4.0;\n" | |
| " v_color = a_color;\n" | |
| "}\n"; | |
| constexpr char kFS[] = | |
| "precision mediump float;\n" | |
| "varying vec4 v_color;\n" | |
| "void main() {\n" | |
| " gl_FragColor = v_color;\n" | |
| "}\n"; | |
| ANGLE_GL_PROGRAM(program, kVS, kFS); | |
| glUseProgram(program); | |
| GLint posLoc = glGetAttribLocation(program, "a_position"); | |
| GLint colorLoc = glGetAttribLocation(program, "a_color"); | |
| ASSERT_NE(-1, posLoc); | |
| ASSERT_NE(-1, colorLoc); | |
| glDisableVertexAttribArray(posLoc); | |
| glDisableVertexAttribArray(colorLoc); | |
| std::vector<GLQueryEXT> queries(kQueryCount); | |
| for (int i = 0; i < kQueryCount; ++i) | |
| { | |
| glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, queries[i]); | |
| if (i % 2 != 0) | |
| { | |
| int idx = i / 2; | |
| GLfloat x = (static_cast<GLfloat>(idx % 30) / 30.0f) * 1.8f - 0.9f; | |
| GLfloat y = (static_cast<GLfloat>((idx / 30) % 30) / 30.0f) * 1.8f - 0.9f; | |
| glVertexAttrib3f(posLoc, x, y, 0.0f); | |
| GLfloat r = static_cast<GLfloat>(i % 256) / 255.0f; | |
| GLfloat g = static_cast<GLfloat>((i * 7) % 256) / 255.0f; | |
| GLfloat b = static_cast<GLfloat>((i * 13) % 256) / 255.0f; | |
| glVertexAttrib4f(colorLoc, r, g, b, 1.0f); | |
| glDrawArrays(GL_POINTS, 0, 1); | |
| } | |
| glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| } | |
| EXPECT_GL_NO_ERROR(); | |
| for (int i = 0; i < kQueryCount; ++i) | |
| { | |
| GLuint result = GL_FALSE; | |
| glGetQueryObjectuivEXT(queries[i], GL_QUERY_RESULT_EXT, &result); | |
| EXPECT_GL_NO_ERROR(); | |
| if (i % 2 != 0) | |
| { | |
| EXPECT_GL_TRUE(result) << " at query index " << i; | |
| } | |
| else | |
| { | |
| EXPECT_GL_FALSE(result) << " at query index " << i; | |
| } | |
| } | |
| } | |
| ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(OcclusionQueriesTest); | |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OcclusionQueriesTestES3); | |
| ANGLE_INSTANTIATE_TEST_ES3_AND( | |
| OcclusionQueriesTestES3, | |
| ES3_VULKAN().enable(Feature::PreferSubmitOnAnySamplesPassedQueryEnd)); | |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OcclusionQueriesNoSurfaceTestES3); | |
| ANGLE_INSTANTIATE_TEST_ES3(OcclusionQueriesNoSurfaceTestES3); |