/*------------------------------------------------------------------------- * OpenGL Conformance Test Suite * ----------------------------- * * Copyright (c) 2014-2019 The Khronos Group Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /*! * \file * \brief */ /*-------------------------------------------------------------------*/ /** * \file gl2fClipControlTests.cpp * \brief Implements conformance tests for "EXT_clip_control" functionality. */ /*-------------------------------------------------------------------*/ #include "es2fClipControlTests.hpp" #include "deSharedPtr.hpp" #include "gluContextInfo.hpp" #include "gluDrawUtil.hpp" #include "gluDefs.hpp" #include "gluPixelTransfer.hpp" #include "gluShaderProgram.hpp" #include "tcuFuzzyImageCompare.hpp" #include "tcuImageCompare.hpp" #include "tcuRenderTarget.hpp" #include "tcuSurface.hpp" #include "tcuTestLog.hpp" #include "glw.h" #include "glwFunctions.hpp" #include namespace deqp { namespace gles2 { namespace Functional { class ClipControlApi { public: ClipControlApi(Context& context) : m_context(context) { if (!Supported(m_context)) { throw tcu::NotSupportedError("Required extension EXT_clip_control is not supported"); } clipControl = context.getRenderContext().getFunctions().clipControl; } static bool Supported(Context& context) { return context.getContextInfo().isExtensionSupported("GL_EXT_clip_control"); } glw::glClipControlFunc clipControl; private: Context& m_context; }; class ClipControlBaseTest : public TestCase { protected: ClipControlBaseTest(Context& context, const char* name, const char* description) : TestCase(context, name, description) { } void init() override { ClipControlApi api(m_context); } bool verifyState(glw::GLenum origin, glw::GLenum depth) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); bool ret = true; glw::GLint retI; gl.getIntegerv(GL_CLIP_ORIGIN, &retI); GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_CLIP_ORIGIN"); ret &= (static_cast(retI) == origin); gl.getIntegerv(GL_CLIP_DEPTH_MODE, &retI); GLU_EXPECT_NO_ERROR(gl.getError(), "get GL_CLIP_DEPTH_MODE"); ret &= (static_cast(retI) == depth); return ret; } }; class ClipControlRenderBaseTest : public ClipControlBaseTest { protected: ClipControlRenderBaseTest(Context& context, const char* name, const char* description) : ClipControlBaseTest(context, name, description), m_fbo(0), m_rboC(0), m_depthTexure(0) { } const char* fsh() { return "void main() {" "\n" " gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);" "\n" "}"; } bool fuzzyDepthCompare(tcu::TestLog& log, const char* imageSetName, const char* imageSetDesc, const tcu::TextureLevel& reference, const tcu::TextureLevel& result, float threshold, const tcu::TextureLevel* importanceMask = NULL) { (void)imageSetName; (void)imageSetDesc; bool depthOk = true; float difference = 0.0f; for (int y = 0; y < result.getHeight() && depthOk; y++) { for (int x = 0; x < result.getWidth() && depthOk; x++) { float ref = reference.getAccess().getPixDepth(x, y); float res = result.getAccess().getPixel(x,y).x(); difference = std::abs(ref - res); if (importanceMask) { difference *= importanceMask->getAccess().getPixDepth(x, y); } depthOk &= (difference < threshold); } } if (!depthOk) log << tcu::TestLog::Message << "Image comparison failed: difference = " << difference << ", threshold = " << threshold << tcu::TestLog::EndMessage; tcu::Vec4 pixelBias(0.0f, 0.0f, 0.0f, 0.0f); tcu::Vec4 pixelScale(1.0f, 1.0f, 1.0f, 1.0f); log << tcu::TestLog::ImageSet("Result", "Depth image comparison result") << tcu::TestLog::Image("Result", "Result", result.getAccess(), pixelScale, pixelBias) << tcu::TestLog::Image("Reference", "Reference", reference.getAccess(), pixelScale, pixelBias); if (importanceMask) { log << tcu::TestLog::Image("Importance mask", "mask", importanceMask->getAccess(), pixelScale, pixelBias); } log << tcu::TestLog::EndImageSet; return depthOk; } virtual void init(void) { const tcu::RenderTarget& renderTarget = m_context.getRenderContext().getRenderTarget(); glw::GLuint viewportW = renderTarget.getWidth(); glw::GLuint viewportH = renderTarget.getHeight(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); gl.genFramebuffers(1, &m_fbo); gl.genRenderbuffers(1, &m_rboC); gl.genTextures(1, &m_depthTexure); gl.bindRenderbuffer(GL_RENDERBUFFER, m_rboC); gl.renderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, viewportW, viewportH); gl.bindTexture(GL_TEXTURE_2D, m_depthTexure); gl.texImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, viewportW, viewportH, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, DE_NULL); gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo); gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rboC); gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_depthTexure, 0); } virtual void deinit(void) { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); gl.deleteFramebuffers(1, &m_fbo); gl.deleteRenderbuffers(1, &m_rboC); gl.deleteTextures(1, &m_depthTexure); gl.bindFramebuffer(GL_FRAMEBUFFER, 0); } GLuint getDepthTexture() { return m_depthTexure; } private: GLuint m_fbo, m_rboC, m_depthTexure; }; /* Verify the following state values are implemented and return a valid initial value by calling GetIntegerv: Get Value Initial Value ------------------------------------------------------- CLIP_ORIGIN LOWER_LEFT CLIP_DEPTH_MODE NEGATIVE_ONE_TO_ONE Verify no GL error is generated. */ class ClipControlInitialState : public ClipControlBaseTest { public: ClipControlInitialState(Context& context, const char* name) : ClipControlBaseTest(context, name, "Verify initial state") { } IterateResult iterate() override { if (!verifyState(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE)) { TCU_FAIL("Wrong intitial state: GL_CLIP_ORIGIN should be GL_LOWER_LEFT," " GL_CLIP_ORIGIN should be NEGATIVE_ONE_TO_ONE"); } m_testCtx.setTestResult(QP_TEST_RESULT_PASS, qpGetTestResultName(QP_TEST_RESULT_PASS)); return STOP; } }; /* Modify the state to each of the following combinations and after each state change verify the state values: ClipControl(UPPER_LEFT, ZERO_TO_ONE) ClipControl(UPPER_LEFT, NEGATIVE_ONE_TO_ONE) ClipControl(LOWER_LEFT, ZERO_TO_ONE) ClipControl(LOWER_LEFT, NEGATIVE_ONE_TO_ONE) Verify no GL error is generated. */ class ClipControlModifyGetState : public ClipControlBaseTest { public: ClipControlModifyGetState(Context& context, const char* name) : ClipControlBaseTest(context, name, "Verify initial state") { } void deinit() override { if (ClipControlApi::Supported(m_context)) { ClipControlApi cc(m_context); cc.clipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); } } IterateResult iterate() override { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); ClipControlApi cc(m_context); GLenum cases[4][2] = { { GL_UPPER_LEFT, GL_ZERO_TO_ONE }, { GL_UPPER_LEFT, GL_NEGATIVE_ONE_TO_ONE }, { GL_LOWER_LEFT, GL_ZERO_TO_ONE }, { GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE }, }; for (size_t i = 0; i < DE_LENGTH_OF_ARRAY(cases); i++) { cc.clipControl(cases[i][0], cases[i][1]); GLU_EXPECT_NO_ERROR(gl.getError(), "ClipControl()"); if (!verifyState(cases[i][0], cases[i][1])) { TCU_FAIL("Wrong ClipControl state after ClipControl() call"); } } m_testCtx.setTestResult(QP_TEST_RESULT_PASS, qpGetTestResultName(QP_TEST_RESULT_PASS)); return STOP; } }; /* Check that ClipControl generate an GL_INVALID_ENUM error if origin is not GL_LOWER_LEFT or GL_UPPER_LEFT. Check that ClipControl generate an GL_INVALID_ENUM error if depth is not GL_NEGATIVE_ONE_TO_ONE or GL_ZERO_TO_ONE. Test is based on OpenGL 4.5 Core Profile Specification May 28th Section 13.5 Primitive Clipping: "An INVALID_ENUM error is generated if origin is not LOWER_LEFT or UPPER_LEFT. An INVALID_ENUM error is generated if depth is not NEGATIVE_ONE_- TO_ONE or ZERO_TO_ONE." */ class ClipControlErrors : public ClipControlBaseTest { public: ClipControlErrors(Context& context, const char* name) : ClipControlBaseTest(context, name, "Verify that proper errors are generated when using ClipControl.") { } void deinit() override { if (ClipControlApi::Supported(m_context)) { ClipControlApi cc(m_context); cc.clipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); } } IterateResult iterate() override { /* API query */ tcu::TestLog& log = m_testCtx.getLog(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); ClipControlApi cc(m_context); /* Finding improper value. */ GLenum improper_value = GL_NONE; while ((GL_UPPER_LEFT == improper_value) || (GL_LOWER_LEFT == improper_value) || (GL_ZERO_TO_ONE == improper_value) || (GL_NEGATIVE_ONE_TO_ONE == improper_value)) { ++improper_value; } /* Test setup. */ GLenum cases[5][2] = { { GL_UPPER_LEFT, improper_value }, { GL_LOWER_LEFT, improper_value }, { improper_value, GL_ZERO_TO_ONE }, { improper_value, GL_NEGATIVE_ONE_TO_ONE }, { improper_value, improper_value } }; /* Test iterations. */ for (size_t i = 0; i < DE_LENGTH_OF_ARRAY(cases); i++) { cc.clipControl(cases[i][0], cases[i][1]); if (GL_INVALID_ENUM != gl.getError()) { m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, qpGetTestResultName(QP_TEST_RESULT_FAIL)); log << tcu::TestLog::Message << "ClipControl have not generated GL_INVALID_ENUM error when called with invalid value (" << cases[i][0] << ", " << cases[i][1] << ")." << tcu::TestLog::EndMessage; } } m_testCtx.setTestResult(QP_TEST_RESULT_PASS, qpGetTestResultName(QP_TEST_RESULT_PASS)); return STOP; } }; /* Clip Control Origin Test * Basic behavior can be tested by rendering to a viewport with clip coordinates where -1.0 <= x_c <= 0.0 and -1.0 <= y_c <= 0.0. When is LOWER_LEFT the "bottom left" portion of the window is rendered and when UPPER_LEFT is used the "top left" portion of the window is rendered. The default framebuffer should be bound. Here is the basic outline of the test: - Clear the default framebuffer to red (1,0,0). - Set ClipControl(UPPER_LEFT, NEGATIVE_ONE_TO_ONE) - Render a triangle fan covering (-1.0, -1.0) to (0.0, 0.0) and write a pixel value of green (0,1,0). - Read back the default framebuffer with ReadPixels - Verify the green pixels at the top and red at the bottom. Repeat the above test with LOWER_LEFT and verify green at the bottom and red at the top. */ class ClipControlOriginTest : public ClipControlRenderBaseTest { public: ClipControlOriginTest(Context& context, const char* name) : ClipControlRenderBaseTest(context, name, "Clip Control Origin Test"), m_vao(0), m_vbo(0) { } void deinit() override { ClipControlRenderBaseTest::deinit(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); if (ClipControlApi::Supported(m_context)) { ClipControlApi cc(m_context); cc.clipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); } gl.clearColor(0.0, 0.0, 0.0, 0.0); if (m_vao) { gl.deleteVertexArrays(1, &m_vao); } if (m_vbo) { gl.deleteBuffers(1, &m_vbo); } } IterateResult iterate() override { tcu::TestLog& log = m_testCtx.getLog(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); ClipControlApi cc(m_context); //Render a triangle fan covering(-1.0, -1.0) to(1.0, 0.0) and //write a pixel value of green(0, 1, 0). de::SharedPtr program( new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vsh(), fsh()))); log << (*program); if (!program->isOk()) { TCU_FAIL("Program compilation failed"); } gl.genVertexArrays(1, &m_vao); gl.bindVertexArray(m_vao); gl.genBuffers(1, &m_vbo); const float vertex_data0[] = { -1.0, -1.0, 0.0, -1.0, -1.0, 0.0, 0.0, 0.0 }; gl.bindBuffer(GL_ARRAY_BUFFER, m_vbo); gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertex_data0), vertex_data0, GL_STATIC_DRAW); gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); gl.enableVertexAttribArray(0); gl.useProgram(program->getProgram()); glw::GLenum origins[] = { GL_UPPER_LEFT, GL_LOWER_LEFT }; qpTestResult result = QP_TEST_RESULT_PASS; for (size_t orig = 0; orig < DE_LENGTH_OF_ARRAY(origins); orig++) { //Clear the default framebuffer to red(1, 0, 0). gl.clearColor(1.0, 0.0, 0.0, 1.0); gl.clear(GL_COLOR_BUFFER_BIT); //Set ClipControl(UPPER_LEFT, NEGATIVE_ONE_TO_ONE) cc.clipControl(origins[orig], GL_NEGATIVE_ONE_TO_ONE); GLU_EXPECT_NO_ERROR(gl.getError(), "ClipControl()"); //test method modification: use GL_TRIANGLE_STRIP, not FAN. gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4); //Read back the default framebuffer with ReadPixels //Verify the green pixels at the top and red at the bottom. qpTestResult loopResult = ValidateFramebuffer(m_context, origins[orig]); if (loopResult != QP_TEST_RESULT_PASS) { result = loopResult; } } m_testCtx.setTestResult(result, qpGetTestResultName(result)); return STOP; } const char* vsh() { return "attribute highp vec2 Position;" "\n" "void main() {" "\n" " gl_Position = vec4(Position, 0.0, 1.0);" "\n" "}"; } qpTestResult ValidateFramebuffer(Context& context, glw::GLenum origin) { const tcu::RenderTarget& renderTarget = context.getRenderContext().getRenderTarget(); glw::GLsizei viewportW = renderTarget.getWidth(); glw::GLsizei viewportH = renderTarget.getHeight(); tcu::Surface renderedFrame(viewportW, viewportH); tcu::Surface referenceFrame(viewportW, viewportH); tcu::TestLog& log = context.getTestContext().getLog(); for (int y = 0; y < renderedFrame.getHeight(); y++) { float yCoord = (float)(y) / (float)renderedFrame.getHeight(); for (int x = 0; x < renderedFrame.getWidth(); x++) { float xCoord = (float)(x) / (float)renderedFrame.getWidth(); bool greenQuadrant; if (origin == GL_UPPER_LEFT) { greenQuadrant = (yCoord > 0.5 && xCoord <= 0.5); } else { greenQuadrant = (yCoord <= 0.5 && xCoord <= 0.5); } if (greenQuadrant) { referenceFrame.setPixel(x, y, tcu::RGBA::green()); } else { referenceFrame.setPixel(x, y, tcu::RGBA::red()); } } } glu::readPixels(context.getRenderContext(), 0, 0, renderedFrame.getAccess()); if (tcu::fuzzyCompare(log, "Result", "Image comparison result", referenceFrame, renderedFrame, 0.05f, tcu::COMPARE_LOG_RESULT)) { return QP_TEST_RESULT_PASS; } else { return QP_TEST_RESULT_FAIL; } } glw::GLuint m_vao, m_vbo; }; /* Clip Control Origin With Face Culling Test * Face culling should be tested with both settings. The reason for that is, when doing Y-inversion, implementation should not flip the calculated area sign for the triangle. In other words, culling of CCW and CW triangles should be orthogonal to used mode. Both triangle windings and both modes should be tested. Here is the basic outline of the test: - Clear the framebuffer to red (1,0,0). - Enable GL_CULL_FACE, leave default front face & cull face (CCW, BACK) - Set ClipControl(UPPER_LEFT, NEGATIVE_ONE_TO_ONE) - Render a counter-clockwise triangles covering (-1.0, -1.0) to (0.0, 1.0) and write a pixel value of green (0,1,0). - Render a clockwise triangles covering (0.0, -1.0) to (1.0, 1.0) and write a pixel value of green (0,1,0). - Read back the framebuffer with ReadPixels - Verify the green pixels at the left and red at the right. Repeat above test for ClipControl(LOWER_LEFT, NEGATIVE_ONE_TO_ONE) */ class ClipControlFaceCulling : public ClipControlRenderBaseTest { public: ClipControlFaceCulling(Context& context, const char* name) : ClipControlRenderBaseTest(context, name, "Face culling test, both origins"), m_vao(0), m_vbo(0) { } void deinit() { ClipControlRenderBaseTest::deinit(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); if (ClipControlApi::Supported(m_context)) { ClipControlApi cc(m_context); cc.clipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); } gl.disable(GL_CULL_FACE); gl.clearColor(0.0, 0.0, 0.0, 0.0); gl.disable(GL_DEPTH_TEST); gl.depthFunc(GL_LESS); if (m_vao) { gl.deleteVertexArrays(1, &m_vao); } if (m_vbo) { gl.deleteBuffers(1, &m_vbo); } } IterateResult iterate() { tcu::TestLog& log = m_testCtx.getLog(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); ClipControlApi cc(m_context); //Enable GL_CULL_FACE, leave default front face & cull face(CCW, BACK) gl.enable(GL_CULL_FACE); //Render a counter-clockwise triangles covering //(-1.0, -1.0) to(0.0, 1.0) and write a pixel value of green(0, 1, 0). //Render a clockwise triangles covering //(0.0, -1.0) to(1.0, 1.0) and write a pixel value of green(0, 1, 0). de::SharedPtr program( new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vsh(), fsh()))); log << (*program); if (!program->isOk()) { TCU_FAIL("Program compilation failed"); } gl.genVertexArrays(1, &m_vao); gl.bindVertexArray(m_vao); gl.genBuffers(1, &m_vbo); const float vertex_data0[] = { //CCW -1.0, -1.0, 0.0, -1.0, -1.0, 1.0, 0.0, -1.0, 0.0, 1.0, -1.0, 1.0, //CW 0.0, -1.0, 0.0, 1.0, 1.0, -1.0, 1.0, -1.0, 0.0, 1.0, 1.0, 1.0, }; gl.bindBuffer(GL_ARRAY_BUFFER, m_vbo); gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertex_data0), vertex_data0, GL_STATIC_DRAW); gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); gl.enableVertexAttribArray(0); gl.useProgram(program->getProgram()); glw::GLenum origins[] = { GL_UPPER_LEFT, GL_LOWER_LEFT }; qpTestResult result = QP_TEST_RESULT_PASS; for (size_t orig = 0; orig < DE_LENGTH_OF_ARRAY(origins); orig++) { //Clear the framebuffer to red (1,0,0). gl.clearColor(1.0, 0.0, 0.0, 1.0); gl.clear(GL_COLOR_BUFFER_BIT); gl.drawArrays(GL_TRIANGLES, 0, 12); //Set ClipControl(, NEGATIVE_ONE_TO_ONE) cc.clipControl(origins[orig], GL_NEGATIVE_ONE_TO_ONE); GLU_EXPECT_NO_ERROR(gl.getError(), "ClipControl()"); //Read back the framebuffer with ReadPixels //Verify the green pixels at the left and red at the right. qpTestResult loopResult = ValidateFramebuffer(m_context); if (loopResult != QP_TEST_RESULT_PASS) { result = loopResult; } } m_testCtx.setTestResult(result, qpGetTestResultName(result)); return STOP; } const char* vsh() { return "attribute highp vec3 Position;" "\n" "void main() {" "\n" " gl_Position = vec4(Position, 1.0);" "\n" "}"; } qpTestResult ValidateFramebuffer(Context& context) { const tcu::RenderTarget& renderTarget = context.getRenderContext().getRenderTarget(); glw::GLsizei viewportW = renderTarget.getWidth(); glw::GLsizei viewportH = renderTarget.getHeight(); tcu::Surface renderedColorFrame(viewportW, viewportH); tcu::Surface referenceColorFrame(viewportW, viewportH); tcu::TestLog& log = context.getTestContext().getLog(); for (int y = 0; y < renderedColorFrame.getHeight(); y++) { for (int x = 0; x < renderedColorFrame.getWidth(); x++) { float xCoord = (float)(x) / (float)renderedColorFrame.getWidth(); if (xCoord < 0.5f) { referenceColorFrame.setPixel(x, y, tcu::RGBA::green()); } else { referenceColorFrame.setPixel(x, y, tcu::RGBA::red()); } } } glu::readPixels(context.getRenderContext(), 0, 0, renderedColorFrame.getAccess()); if (!tcu::fuzzyCompare(log, "Result", "Color image comparison result", referenceColorFrame, renderedColorFrame, 0.05f, tcu::COMPARE_LOG_RESULT)) { return QP_TEST_RESULT_FAIL; } return QP_TEST_RESULT_PASS; } glw::GLuint m_vao, m_vbo; }; /* Viewport Bounds Test * Viewport bounds should be tested, to ensure that rendering with flipped origin affects only viewport area. This can be done by clearing the window to blue, making viewport a non-symmetric-in-any-way subset of the window, than rendering full-viewport multiple color quad. The (-1.0, -1.0)..(0.0, 0.0) quadrant of a quad is red, the rest is green. Whatever the origin is, the area outside of the viewport should stay blue. If origin is LOWER_LEFT the "lower left" portion of the viewport is red, if origin is UPPER_LEFT the "top left" portion of the viewport is red (and in both cases the rest of viewport is green). Here is the basic outline of the test: - Clear the default framebuffer to blue (0,0,1). - Set viewport to A = (x, y, w, h) = (1/8, 1/4, 1/2, 1/4) in terms of proportional window size - Set ClipControl(UPPER_LEFT, NEGATIVE_ONE_TO_ONE) - Render a triangle strip covering (-1.0, -1.0) to (1.0, 1.0). Write a pixel value of red (0,1,0) to (-1.0, -1.0)..(0.0, 0.0), other parts are green - Reset viewport to defaults - Read back the default framebuffer with ReadPixels - Verify: - regions outside A viewport are green - Inside A viewport upper upper left portion is red, rest is green. Repeat the above test with LOWER_LEFT origin and lower left portion of A is red, rest is green. */ class ClipControlViewportBounds : public ClipControlRenderBaseTest { public: ClipControlViewportBounds(Context& context, const char* name) : ClipControlRenderBaseTest(context, name, "Clip Control Origin Test"), m_vao(0), m_vbo(0) { } void deinit() override { ClipControlRenderBaseTest::deinit(); const tcu::RenderTarget& renderTarget = m_context.getRenderContext().getRenderTarget(); glw::GLsizei windowW = renderTarget.getWidth(); glw::GLsizei windowH = renderTarget.getHeight(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); if (ClipControlApi::Supported(m_context)) { ClipControlApi cc(m_context); cc.clipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); } gl.clearColor(0.0, 0.0, 0.0, 0.0); gl.viewport(0, 0, windowW, windowH); if (m_vao) { gl.deleteVertexArrays(1, &m_vao); } if (m_vbo) { gl.deleteBuffers(1, &m_vbo); } } IterateResult iterate() override { tcu::TestLog& log = m_testCtx.getLog(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); const tcu::RenderTarget& renderTarget = m_context.getRenderContext().getRenderTarget(); glw::GLsizei windowW = renderTarget.getWidth(); glw::GLsizei windowH = renderTarget.getHeight(); ClipControlApi cc(m_context); //Clear the default framebuffer to blue (0,0,1). gl.clearColor(0.0, 0.0, 1.0, 1.0); gl.clear(GL_COLOR_BUFFER_BIT); de::SharedPtr program( new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vsh(), fsh()))); log << (*program); if (!program->isOk()) { TCU_FAIL("Program compilation failed"); } gl.genVertexArrays(1, &m_vao); gl.bindVertexArray(m_vao); gl.genBuffers(1, &m_vbo); const float vertex_data0[] = { -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0 }; gl.bindBuffer(GL_ARRAY_BUFFER, m_vbo); gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertex_data0), vertex_data0, GL_STATIC_DRAW); gl.vertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); gl.enableVertexAttribArray(0); gl.useProgram(program->getProgram()); glw::GLenum origins[] = { GL_UPPER_LEFT, GL_LOWER_LEFT }; qpTestResult result = QP_TEST_RESULT_PASS; for (size_t orig = 0; orig < DE_LENGTH_OF_ARRAY(origins); orig++) { //Set viewport to A = (x, y, w, h) = (1/8, 1/4, 1/2, 1/4) in terms of proportional window size gl.viewport(static_cast((0.125f * static_cast(windowW))+0.5f), static_cast((0.25f * static_cast(windowH))+0.5f), static_cast((0.5f * static_cast(windowW))+0.5f), static_cast((0.25f * static_cast(windowH))+0.5f)); //Set ClipControl(, NEGATIVE_ONE_TO_ONE) cc.clipControl(origins[orig], GL_NEGATIVE_ONE_TO_ONE); GLU_EXPECT_NO_ERROR(gl.getError(), "ClipControl()"); //Render a triangle strip covering (-1.0, -1.0) to (1.0, 1.0). //Write a pixel value of red (0,1,0) to (-1.0, -1.0)..(0.0, 0.0), other parts are green gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4); gl.viewport(0, 0, windowW, windowH); //Read back the default framebuffer with ReadPixels //Verify the green pixels at the top and red at the bottom. qpTestResult loopResult = ValidateFramebuffer(m_context, origins[orig]); if (loopResult != QP_TEST_RESULT_PASS) { result = loopResult; } } m_testCtx.setTestResult(result, qpGetTestResultName(result)); return STOP; } const char* vsh() { return "attribute highp vec2 Position;" "\n" "varying highp vec2 PositionOut;" "\n" "void main() {" "\n" " gl_Position = vec4(Position, 0.0, 1.0);" "\n" " PositionOut = Position;" "\n" "}"; } const char* fsh() { return "varying highp vec2 PositionOut;" "\n" "void main() {" "\n" " if (PositionOut.x < 0.0 && PositionOut.y < 0.0)" "\n" " gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);" "\n" " else" "\n" " gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);" "\n" "}"; } qpTestResult ValidateFramebuffer(Context& context, glw::GLenum origin) { const tcu::RenderTarget& renderTarget = context.getRenderContext().getRenderTarget(); glw::GLsizei windowW = renderTarget.getWidth(); glw::GLsizei windowH = renderTarget.getHeight(); tcu::Surface renderedFrame(windowW, windowH); tcu::Surface referenceFrame(windowW, windowH); tcu::TestLog& log = context.getTestContext().getLog(); for (int y = 0; y < renderedFrame.getHeight(); y++) { float yCoord = static_cast(y) / static_cast(renderedFrame.getHeight()); float yVPCoord = (yCoord - 0.25f) * 4.0f; for (int x = 0; x < renderedFrame.getWidth(); x++) { float xCoord = static_cast(x) / static_cast(renderedFrame.getWidth()); float xVPCoord = (xCoord - 0.125f) * 2.0f; if (xVPCoord > 0.0f && xVPCoord < 1.0f && yVPCoord > 0.0f && yVPCoord < 1.0f) { bool greenQuadrant; //inside viewport if (origin == GL_UPPER_LEFT) { greenQuadrant = (yVPCoord > 0.5f && xVPCoord <= 0.5f); } else { greenQuadrant = (yVPCoord <= 0.5f && xVPCoord <= 0.5f); } if (greenQuadrant) { referenceFrame.setPixel(x, y, tcu::RGBA::green()); } else { referenceFrame.setPixel(x, y, tcu::RGBA::red()); } } else { //outside viewport referenceFrame.setPixel(x, y, tcu::RGBA::blue()); } } } glu::readPixels(context.getRenderContext(), 0, 0, renderedFrame.getAccess()); if (tcu::fuzzyCompare(log, "Result", "Image comparison result", referenceFrame, renderedFrame, 0.05f, tcu::COMPARE_LOG_RESULT)) { return QP_TEST_RESULT_PASS; } else { return QP_TEST_RESULT_FAIL; } } glw::GLuint m_vao, m_vbo; }; /* Depth Mode Test * Basic behavior can be tested by writing specific z_c (z clip coordinates) and observing its clipping and transformation. Create and bind a framebuffer object with a floating-point depth buffer attachment. Make sure depth clamping is disabled. The best steps for verifying the correct depth mode: - Clear the depth buffer to 0.5. - Set ClipControl(LOWER_LEFT, ZERO_TO_ONE) - Enable(DEPTH_TEST) with DepthFunc(ALWAYS) - Render a triangle fan coverage (-1.0,-1.0,-1.0) to (1.0,1.0,1.0). - Read back the floating-point depth buffer with ReadPixels - Verify that the pixels with a Z clip coordinate less than 0.0 are clipped and those coordinates from 0.0 to 1.0 update the depth buffer with values 0.0 to 1.0. */ class ClipControlDepthModeTest : public ClipControlRenderBaseTest { public: ClipControlDepthModeTest(Context& context, const char* name, const char* subname) : ClipControlRenderBaseTest(context, name, subname) { } void init() override { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); const tcu::RenderTarget& renderTarget = m_context.getRenderContext().getRenderTarget(); glw::GLuint viewportW = renderTarget.getWidth(); glw::GLuint viewportH = renderTarget.getHeight(); ClipControlRenderBaseTest::init(); gl.genFramebuffers(1, &m_fboD); gl.genTextures(1, &m_texDepthResolve); gl.bindTexture(GL_TEXTURE_2D, m_texDepthResolve); setupTexture(); gl.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, viewportW, viewportH, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); } void deinit() override { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); gl.deleteTextures(1, &m_texDepthResolve); gl.deleteFramebuffers(1, &m_fboD); ClipControlRenderBaseTest::deinit(); } void setupTexture() { const glw::Functions& gl = m_context.getRenderContext().getFunctions(); gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } void readDepthPixels(const tcu::PixelBufferAccess& pixelBuf) { const char* vs = "\n" "attribute vec4 pos;\n" "attribute vec2 UV;\n" "varying highp vec2 vUV;\n" "void main() {\n" " gl_Position = pos;\n" " vUV = UV;\n" "}\n"; const char* fs = "\n" "precision mediump float;\n" "varying vec2 vUV;\n" "uniform sampler2D tex;\n" "void main() {\n" " gl_FragColor = texture2D(tex, vUV).rrrr;\n" "}\n"; const glu::RenderContext& renderContext = m_context.getRenderContext(); const glw::Functions& gl = renderContext.getFunctions(); const tcu::RenderTarget& renderTarget = renderContext.getRenderTarget(); glw::GLsizei windowW = renderTarget.getWidth(); glw::GLsizei windowH = renderTarget.getHeight(); glu::ShaderProgram program(renderContext, glu::makeVtxFragSources(vs, fs)); gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboD); gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texDepthResolve, 0); gl.disable(GL_DEPTH_TEST); gl.depthMask(GL_FALSE); gl.disable(GL_STENCIL_TEST); gl.viewport(0, 0, windowW, windowH); gl.clearColor(0.0f, 0.2f, 1.0f, 1.0f); gl.clear(GL_COLOR_BUFFER_BIT); const int texLoc = gl.getUniformLocation(program.getProgram(), "tex"); gl.bindVertexArray(0); gl.bindBuffer(GL_ARRAY_BUFFER, 0); gl.bindTexture(GL_TEXTURE_2D, getDepthTexture()); setupTexture(); gl.useProgram(program.getProgram()); gl.uniform1i(texLoc, 0); { const GLfloat vertices[] = { -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, }; const GLfloat texCoords[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, }; const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 }; const glu::VertexArrayBinding vertexArray[] = { glu::va::Float("pos", 4, 4, 0, vertices), glu::va::Float("UV", 2, 4, 0, texCoords) }; glu::draw(renderContext, program.getProgram(), 2, vertexArray, glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), indices)); } glu::readPixels(renderContext, 0, 0, pixelBuf); } GLuint m_fboD; GLuint m_texDepthResolve; }; class ClipControlDepthModeZeroToOneTest : public ClipControlDepthModeTest { public: ClipControlDepthModeZeroToOneTest(Context& context, const char* name) : ClipControlDepthModeTest(context, name, "Depth Mode Test, ZERO_TO_ONE"), m_vao(0), m_vbo(0) { } void deinit() override { ClipControlDepthModeTest::deinit(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); if (ClipControlApi::Supported(m_context)) { ClipControlApi cc(m_context); cc.clipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); } gl.clearDepthf(0.0f); gl.clearColor(0.0, 0.0, 0.0, 0.0); gl.disable(GL_DEPTH_TEST); gl.depthFunc(GL_LESS); if (m_vao) { gl.deleteVertexArrays(1, &m_vao); } if (m_vbo) { gl.deleteBuffers(1, &m_vbo); } } IterateResult iterate() override { tcu::TestLog& log = m_testCtx.getLog(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); ClipControlApi cc(m_context); gl.clearColor(1.0, 0.0, 0.0, 1.0); gl.clear(GL_COLOR_BUFFER_BIT); //Clear the depth buffer to 0.5. gl.clearDepthf(0.5); gl.clear(GL_DEPTH_BUFFER_BIT); //Set ClipControl(LOWER_LEFT, ZERO_TO_ONE) cc.clipControl(GL_LOWER_LEFT, GL_ZERO_TO_ONE); GLU_EXPECT_NO_ERROR(gl.getError(), "ClipControl()"); //Enable(DEPTH_TEST) with DepthFunc(ALWAYS) gl.enable(GL_DEPTH_TEST); gl.depthFunc(GL_ALWAYS); //Render a triangle fan coverage (-1.0,-1.0,-1.0) to (1.0,1.0,1.0). de::SharedPtr program( new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vsh(), fsh()))); log << (*program); if (!program->isOk()) { TCU_FAIL("Program compilation failed"); } gl.genVertexArrays(1, &m_vao); gl.bindVertexArray(m_vao); gl.genBuffers(1, &m_vbo); const float vertex_data0[] = { -1.0, -1.0, -1.0, 1.0, -1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, }; gl.bindBuffer(GL_ARRAY_BUFFER, m_vbo); gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertex_data0), vertex_data0, GL_STATIC_DRAW); gl.vertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); gl.enableVertexAttribArray(0); gl.useProgram(program->getProgram()); //test method modification: use GL_TRIANGLE_STRIP, not FAN. gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4); //Read back the floating-point depth buffer with ReadPixels //Verify that the pixels with a Z clip coordinate less than 0.0 are // clipped and those coordinates from 0.0 to 1.0 update the depth // buffer with values 0.0 to 1.0. qpTestResult result = ValidateFramebuffer(m_context); m_testCtx.setTestResult(result, qpGetTestResultName(result)); return STOP; } const char* vsh() { return "attribute vec3 Position;" "\n" "void main() {" "\n" " gl_Position = vec4(Position, 1.0);" "\n" "}"; } qpTestResult ValidateFramebuffer(Context& context) { const tcu::RenderTarget& renderTarget = context.getRenderContext().getRenderTarget(); glw::GLuint viewportW = renderTarget.getWidth(); glw::GLuint viewportH = renderTarget.getHeight(); tcu::Surface renderedColorFrame(viewportW, viewportH); tcu::Surface referenceColorFrame(viewportW, viewportH); tcu::TextureFormat depthReadbackFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8); tcu::TextureFormat depthFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16); tcu::TextureLevel renderedDepthFrame(depthReadbackFormat, viewportW, viewportH); tcu::TextureLevel referenceDepthFrame(depthFormat, viewportW, viewportH); tcu::TextureLevel importanceMaskFrame(depthFormat, viewportW, viewportH); tcu::TestLog& log = context.getTestContext().getLog(); const float rasterizationError = 2.0f / (float)renderedColorFrame.getHeight() + 2.0f / (float)renderedColorFrame.getWidth(); for (int y = 0; y < renderedColorFrame.getHeight(); y++) { float yCoord = ((float)(y) + 0.5f) / (float)renderedColorFrame.getHeight(); for (int x = 0; x < renderedColorFrame.getWidth(); x++) { float xCoord = ((float)(x) + 0.5f) / (float)renderedColorFrame.getWidth(); if (yCoord >= 1.0 - xCoord - rasterizationError && yCoord <= 1.0 - xCoord + rasterizationError) { importanceMaskFrame.getAccess().setPixDepth(0.0f, x, y); } else { importanceMaskFrame.getAccess().setPixDepth(1.0f, x, y); } if (yCoord < 1.0 - xCoord) { referenceColorFrame.setPixel(x, y, tcu::RGBA::red()); referenceDepthFrame.getAccess().setPixDepth(0.5f, x, y); } else { referenceColorFrame.setPixel(x, y, tcu::RGBA::green()); referenceDepthFrame.getAccess().setPixDepth(-1.0f + xCoord + yCoord, x, y); } } } glu::readPixels(context.getRenderContext(), 0, 0, renderedColorFrame.getAccess()); if (!tcu::fuzzyCompare(log, "Result", "Color image comparison result", referenceColorFrame, renderedColorFrame, 0.05f, tcu::COMPARE_LOG_RESULT)) { return QP_TEST_RESULT_FAIL; } readDepthPixels(renderedDepthFrame.getAccess()); if (!fuzzyDepthCompare(log, "Result", "Depth image comparison result", referenceDepthFrame, renderedDepthFrame, 0.05f, &importanceMaskFrame)) { return QP_TEST_RESULT_FAIL; } return QP_TEST_RESULT_PASS; } glw::GLuint m_vao, m_vbo; }; /* Do the same as above, but use the default NEGATIVE_ONE_TO_ONE depth mode: - Clear the depth buffer to 0.5. - Set ClipControl(LOWER_LEFT, NEGATIVE_ONE_TO_ONE) - Enable(DEPTH_TEST) with DepthFunc(ALWAYS) - Render a triangle fan coverage (-1.0,-1.0,-1.0) to (1.0,1.0,1.0). - Read back the floating-point depth buffer with ReadPixels - Verify that no pixels are clipped and the depth buffer contains values from 0.0 to 1.0. */ class ClipControlDepthModeOneToOneTest : public ClipControlDepthModeTest { public: ClipControlDepthModeOneToOneTest(Context& context, const char* name) : ClipControlDepthModeTest(context, name, "Depth Mode Test, ONE_TO_ONE"), m_vao(0), m_vbo(0) { } void deinit() override { ClipControlDepthModeTest::deinit(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); if (ClipControlApi::Supported(m_context)) { ClipControlApi cc(m_context); cc.clipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); } gl.clearDepthf(0.0); gl.clearColor(0.0, 0.0, 0.0, 0.0); gl.disable(GL_DEPTH_TEST); gl.depthFunc(GL_LESS); if (m_vao) { gl.deleteVertexArrays(1, &m_vao); } if (m_vbo) { gl.deleteBuffers(1, &m_vbo); } } IterateResult iterate() override { tcu::TestLog& log = m_testCtx.getLog(); const glw::Functions& gl = m_context.getRenderContext().getFunctions(); ClipControlApi cc(m_context); gl.clearColor(1.0, 0.0, 0.0, 1.0); gl.clear(GL_COLOR_BUFFER_BIT); //Clear the depth buffer to 0.5. gl.clearDepthf(0.5f); gl.clear(GL_DEPTH_BUFFER_BIT); //Set ClipControl(LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE) cc.clipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); GLU_EXPECT_NO_ERROR(gl.getError(), "ClipControl()"); //Enable(DEPTH_TEST) with DepthFunc(ALWAYS) gl.enable(GL_DEPTH_TEST); gl.depthFunc(GL_ALWAYS); //Render a triangle fan coverage (-1.0,-1.0,-1.0) to (1.0,1.0,1.0). de::SharedPtr program( new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(vsh(), fsh()))); log << (*program); if (!program->isOk()) { TCU_FAIL("Program compilation failed"); } gl.genVertexArrays(1, &m_vao); gl.bindVertexArray(m_vao); gl.genBuffers(1, &m_vbo); const float vertex_data0[] = { -1.0, -1.0, -1.0, 1.0, -1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, }; gl.bindBuffer(GL_ARRAY_BUFFER, m_vbo); gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertex_data0), vertex_data0, GL_STATIC_DRAW); gl.vertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); gl.enableVertexAttribArray(0); gl.useProgram(program->getProgram()); //test method modification: use GL_TRIANGLE_STRIP, not FAN. gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4); //Read back the floating-point depth buffer with ReadPixels //Verify that the pixels with a Z clip coordinate less than 0.0 are // clipped and those coordinates from 0.0 to 1.0 update the depth // buffer with values 0.0 to 1.0. qpTestResult result = ValidateFramebuffer(m_context); m_testCtx.setTestResult(result, qpGetTestResultName(result)); return STOP; } const char* vsh() { return "attribute vec3 Position;" "\n" "void main() {" "\n" " gl_Position = vec4(Position, 1.0);" "\n" "}"; } qpTestResult ValidateFramebuffer(Context& context) { const tcu::RenderTarget& renderTarget = context.getRenderContext().getRenderTarget(); glw::GLuint viewportW = renderTarget.getWidth(); glw::GLuint viewportH = renderTarget.getHeight(); tcu::Surface renderedColorFrame(viewportW, viewportH); tcu::Surface referenceColorFrame(viewportW, viewportH); tcu::TextureFormat depthReadbackFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8); tcu::TextureFormat depthFormat(tcu::TextureFormat::D, tcu::TextureFormat::UNORM_INT16); tcu::TextureLevel renderedDepthFrame(depthReadbackFormat, viewportW, viewportH); tcu::TextureLevel referenceDepthFrame(depthFormat, viewportW, viewportH); tcu::TestLog& log = context.getTestContext().getLog(); for (int y = 0; y < renderedColorFrame.getHeight(); y++) { float yCoord = (float)(y) / (float)renderedColorFrame.getHeight(); for (int x = 0; x < renderedColorFrame.getWidth(); x++) { float xCoord = (float)(x) / (float)renderedColorFrame.getWidth(); referenceColorFrame.setPixel(x, y, tcu::RGBA::green()); referenceDepthFrame.getAccess().setPixDepth((xCoord + yCoord) * 0.5f, x, y); } } glu::readPixels(context.getRenderContext(), 0, 0, renderedColorFrame.getAccess()); if (!tcu::fuzzyCompare(log, "Result", "Color image comparison result", referenceColorFrame, renderedColorFrame, 0.05f, tcu::COMPARE_LOG_RESULT)) { return QP_TEST_RESULT_FAIL; } readDepthPixels(renderedDepthFrame.getAccess()); if (!fuzzyDepthCompare(log, "Result", "Depth image comparison result", referenceDepthFrame, renderedDepthFrame, 0.05f)) { return QP_TEST_RESULT_FAIL; } return QP_TEST_RESULT_PASS; } glw::GLuint m_vao, m_vbo; }; /** Constructor. * * @param context Rendering context. **/ ClipControlTests::ClipControlTests(Context& context) : TestCaseGroup(context, "clip_control", "Verifies \"clip_control\" functionality") { /* Left blank on purpose */ } /** Destructor. * **/ ClipControlTests::~ClipControlTests() { } /** Initializes a texture_storage_multisample test group. * **/ void ClipControlTests::init(void) { addChild(new ClipControlInitialState(m_context, "initial")); addChild(new ClipControlModifyGetState(m_context, "modify_get")); addChild(new ClipControlErrors(m_context, "errors")); addChild(new ClipControlOriginTest(m_context, "origin")); addChild(new ClipControlDepthModeZeroToOneTest(m_context, "depth_mode_zero_to_one")); addChild(new ClipControlDepthModeOneToOneTest(m_context, "depth_mode_one_to_one")); addChild(new ClipControlFaceCulling(m_context, "face_culling")); addChild(new ClipControlViewportBounds(m_context, "viewport_bounds")); } } } }