Edit

kc3-lang/angle/src/tests/perf_tests/DrawCallPerf.cpp

Branch :

  • Show log

    Commit

  • Author : Tim Van Patten
    Date : 2021-09-09 16:47:26
    Hash : c78ebccd
    Message : Perf: Add _many_tex_draw test Add a new perf test _many_tex_draw, which draws with 8 textures bound. The intent of this test is to stress calls like retain() with various implementations of vk::Resource. Additionally, this CL updates the textures to use format GL_RGBA, rather than GL_RGB, to avoid the emulation step that's required due to the lack of support for the 3 channel format. Bug: angleproject:5971 Test: DrawCallPerfBenchmark.Run/*_many_tex_draw Change-Id: Iffb39b76fab68cc2a76dfd2da38db7e77cb4dac0 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3152171 Reviewed-by: Jamie Madill <jmadill@chromium.org> Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: Charlie Lao <cclao@google.com> Commit-Queue: Tim Van Patten <timvp@google.com>

  • src/tests/perf_tests/DrawCallPerf.cpp
  • //
    // Copyright 2014 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.
    //
    // DrawCallPerf:
    //   Performance tests for ANGLE draw call overhead.
    //
    
    #include "ANGLEPerfTest.h"
    #include "DrawCallPerfParams.h"
    #include "common/PackedEnums.h"
    #include "test_utils/draw_call_perf_utils.h"
    #include "util/shader_utils.h"
    
    namespace
    {
    enum class StateChange
    {
        NoChange,
        VertexAttrib,
        VertexBuffer,
        ManyVertexBuffers,
        Texture,
        Program,
        VertexBufferCycle,
        Scissor,
        ManyTextureDraw,
        InvalidEnum,
        EnumCount = InvalidEnum,
    };
    
    constexpr size_t kCycleVBOPoolSize  = 200;
    constexpr size_t kManyTexturesCount = 8;
    
    struct DrawArraysPerfParams : public DrawCallPerfParams
    {
        DrawArraysPerfParams() = default;
        DrawArraysPerfParams(const DrawCallPerfParams &base) : DrawCallPerfParams(base) {}
    
        std::string story() const override;
    
        StateChange stateChange = StateChange::NoChange;
    };
    
    std::string DrawArraysPerfParams::story() const
    {
        std::stringstream strstr;
    
        strstr << DrawCallPerfParams::story();
    
        switch (stateChange)
        {
            case StateChange::VertexAttrib:
                strstr << "_attrib_change";
                break;
            case StateChange::VertexBuffer:
                strstr << "_vbo_change";
                break;
            case StateChange::ManyVertexBuffers:
                strstr << "_manyvbos_change";
                break;
            case StateChange::Texture:
                strstr << "_tex_change";
                break;
            case StateChange::Program:
                strstr << "_prog_change";
                break;
            case StateChange::VertexBufferCycle:
                strstr << "_vbo_cycle";
                break;
            case StateChange::Scissor:
                strstr << "_scissor_change";
                break;
            case StateChange::ManyTextureDraw:
                strstr << "_many_tex_draw";
                break;
            default:
                break;
        }
    
        return strstr.str();
    }
    
    std::ostream &operator<<(std::ostream &os, const DrawArraysPerfParams &params)
    {
        os << params.backendAndStory().substr(1);
        return os;
    }
    
    GLuint CreateSimpleTexture2D()
    {
        // Use tightly packed data
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    
        // Generate a texture object
        GLuint texture;
        glGenTextures(1, &texture);
    
        // Bind the texture object
        glBindTexture(GL_TEXTURE_2D, texture);
    
        // Load the texture: 2x2 Image, 3 bytes per pixel (R, G, B)
        constexpr size_t width             = 2;
        constexpr size_t height            = 2;
        GLubyte pixels[width * height * 4] = {
            255, 0,   0,   0,  // Red
            0,   255, 0,   0,  // Green
            0,   0,   255, 0,  // Blue
            255, 255, 0,   0   // Yellow
        };
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    
        // Set the filtering mode
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    
        return texture;
    }
    
    class DrawCallPerfBenchmark : public ANGLERenderTest,
                                  public ::testing::WithParamInterface<DrawArraysPerfParams>
    {
      public:
        DrawCallPerfBenchmark();
    
        void initializeBenchmark() override;
        void destroyBenchmark() override;
        void drawBenchmark() override;
    
      private:
        GLuint mProgram1   = 0;
        GLuint mProgram2   = 0;
        GLuint mProgram3   = 0;
        GLuint mBuffer1    = 0;
        GLuint mBuffer2    = 0;
        GLuint mFBO        = 0;
        GLuint mFBOTexture = 0;
        std::vector<GLuint> mTextures;
        int mNumTris = GetParam().numTris;
        std::vector<GLuint> mVBOPool;
        size_t mCurrentVBO = 0;
    };
    
    DrawCallPerfBenchmark::DrawCallPerfBenchmark() : ANGLERenderTest("DrawCallPerf", GetParam()) {}
    
    void DrawCallPerfBenchmark::initializeBenchmark()
    {
        const auto &params = GetParam();
    
        if (params.stateChange == StateChange::Texture)
        {
            mProgram1 = SetupSimpleTextureProgram();
            ASSERT_NE(0u, mProgram1);
        }
        else if (params.stateChange == StateChange::ManyTextureDraw)
        {
            mProgram3 = SetupEightTextureProgram();
            ASSERT_NE(0u, mProgram3);
        }
        else if (params.stateChange == StateChange::Program)
        {
            mProgram1 = SetupSimpleTextureProgram();
            mProgram2 = SetupDoubleTextureProgram();
            ASSERT_NE(0u, mProgram1);
            ASSERT_NE(0u, mProgram2);
        }
        else if (params.stateChange == StateChange::ManyVertexBuffers)
        {
            constexpr char kVS[] = R"(attribute vec2 vPosition;
    attribute vec2 v0;
    attribute vec2 v1;
    attribute vec2 v2;
    attribute vec2 v3;
    const float scale = 0.5;
    const float offset = -0.5;
    
    varying vec2 v;
    
    void main()
    {
        gl_Position = vec4(vPosition * vec2(scale) + vec2(offset), 0, 1);
        v = (v0 + v1 + v2 + v3) * 0.25;
    })";
    
            constexpr char kFS[] = R"(precision mediump float;
    varying vec2 v;
    void main()
    {
        gl_FragColor = vec4(v, 0, 1);
    })";
    
            mProgram1 = CompileProgram(kVS, kFS);
            ASSERT_NE(0u, mProgram1);
            glBindAttribLocation(mProgram1, 1, "v0");
            glBindAttribLocation(mProgram1, 2, "v1");
            glBindAttribLocation(mProgram1, 3, "v2");
            glBindAttribLocation(mProgram1, 4, "v3");
            glEnableVertexAttribArray(1);
            glEnableVertexAttribArray(2);
            glEnableVertexAttribArray(3);
            glEnableVertexAttribArray(4);
        }
        else if (params.stateChange == StateChange::VertexBufferCycle)
        {
            mProgram1 = SetupSimpleDrawProgram();
            ASSERT_NE(0u, mProgram1);
    
            for (size_t bufferIndex = 0; bufferIndex < kCycleVBOPoolSize; ++bufferIndex)
            {
                GLuint buffer = Create2DTriangleBuffer(mNumTris, GL_STATIC_DRAW);
                mVBOPool.push_back(buffer);
            }
        }
        else
        {
            mProgram1 = SetupSimpleDrawProgram();
            ASSERT_NE(0u, mProgram1);
        }
    
        // Re-link program to ensure the attrib bindings are used.
        if (mProgram1)
        {
            glBindAttribLocation(mProgram1, 0, "vPosition");
            glLinkProgram(mProgram1);
            glUseProgram(mProgram1);
        }
    
        if (mProgram2)
        {
            glBindAttribLocation(mProgram2, 0, "vPosition");
            glLinkProgram(mProgram2);
        }
    
        if (mProgram3)
        {
            glBindAttribLocation(mProgram3, 0, "vPosition");
            glLinkProgram(mProgram3);
        }
    
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    
        mBuffer1 = Create2DTriangleBuffer(mNumTris, GL_STATIC_DRAW);
        mBuffer2 = Create2DTriangleBuffer(mNumTris, GL_STATIC_DRAW);
    
        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
        glEnableVertexAttribArray(0);
    
        // Set the viewport
        glViewport(0, 0, getWindow()->getWidth(), getWindow()->getHeight());
    
        if (params.surfaceType == SurfaceType::Offscreen)
        {
            CreateColorFBO(getWindow()->getWidth(), getWindow()->getHeight(), &mFBOTexture, &mFBO);
        }
    
        for (size_t i = 0; i < kManyTexturesCount; ++i)
        {
            mTextures.emplace_back(CreateSimpleTexture2D());
        }
    
        if (params.stateChange == StateChange::Program)
        {
            // Bind the textures as appropriate, they are not modified during the test.
            GLint program1Tex1Loc = glGetUniformLocation(mProgram1, "tex");
            GLint program2Tex1Loc = glGetUniformLocation(mProgram2, "tex1");
            GLint program2Tex2Loc = glGetUniformLocation(mProgram2, "tex2");
    
            glUseProgram(mProgram1);
            glUniform1i(program1Tex1Loc, 0);
    
            glUseProgram(mProgram2);
            glUniform1i(program2Tex1Loc, 0);
            glUniform1i(program2Tex2Loc, 1);
        }
    
        if (params.stateChange == StateChange::ManyTextureDraw)
        {
            GLint program3TexLocs[kManyTexturesCount];
    
            for (size_t i = 0; i < mTextures.size(); ++i)
            {
                char stringBuffer[8];
                snprintf(stringBuffer, sizeof(stringBuffer), "tex%zu", i);
                program3TexLocs[i] = glGetUniformLocation(mProgram3, stringBuffer);
            }
    
            glUseProgram(mProgram3);
            for (size_t i = 0; i < mTextures.size(); ++i)
            {
                glUniform1i(program3TexLocs[i], i);
            }
    
            for (size_t i = 0; i < mTextures.size(); ++i)
            {
                glActiveTexture(GL_TEXTURE0 + i);
                glBindTexture(GL_TEXTURE_2D, mTextures[i]);
            }
        }
    
        ASSERT_GL_NO_ERROR();
    }
    
    void DrawCallPerfBenchmark::destroyBenchmark()
    {
        glDeleteProgram(mProgram1);
        glDeleteProgram(mProgram2);
        glDeleteProgram(mProgram3);
        glDeleteBuffers(1, &mBuffer1);
        glDeleteBuffers(1, &mBuffer2);
        glDeleteTextures(1, &mFBOTexture);
        glDeleteTextures(mTextures.size(), mTextures.data());
        glDeleteFramebuffers(1, &mFBO);
    
        if (!mVBOPool.empty())
        {
            glDeleteBuffers(mVBOPool.size(), mVBOPool.data());
        }
    }
    
    void ClearThenDraw(unsigned int iterations, GLsizei numElements)
    {
        glClear(GL_COLOR_BUFFER_BIT);
    
        for (unsigned int it = 0; it < iterations; it++)
        {
            glDrawArrays(GL_TRIANGLES, 0, numElements);
        }
    }
    
    void JustDraw(unsigned int iterations, GLsizei numElements)
    {
        for (unsigned int it = 0; it < iterations; it++)
        {
            glDrawArrays(GL_TRIANGLES, 0, numElements);
        }
    }
    
    template <int kArrayBufferCount>
    void ChangeVertexAttribThenDraw(unsigned int iterations, GLsizei numElements, GLuint buffer)
    {
        glBindBuffer(GL_ARRAY_BUFFER, buffer);
        for (unsigned int it = 0; it < iterations; it++)
        {
            for (int arrayIndex = 0; arrayIndex < kArrayBufferCount; ++arrayIndex)
            {
                glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, 0);
            }
            glDrawArrays(GL_TRIANGLES, 0, numElements);
    
            for (int arrayIndex = 0; arrayIndex < kArrayBufferCount; ++arrayIndex)
            {
                glVertexAttribPointer(arrayIndex, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0);
            }
            glDrawArrays(GL_TRIANGLES, 0, numElements);
        }
    }
    template <int kArrayBufferCount>
    void ChangeArrayBuffersThenDraw(unsigned int iterations,
                                    GLsizei numElements,
                                    GLuint buffer1,
                                    GLuint buffer2)
    {
        for (unsigned int it = 0; it < iterations; it++)
        {
            glBindBuffer(GL_ARRAY_BUFFER, buffer1);
            for (int arrayIndex = 0; arrayIndex < kArrayBufferCount; ++arrayIndex)
            {
                glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, 0);
            }
            glDrawArrays(GL_TRIANGLES, 0, numElements);
    
            glBindBuffer(GL_ARRAY_BUFFER, buffer2);
            for (int arrayIndex = 0; arrayIndex < kArrayBufferCount; ++arrayIndex)
            {
                glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, 0);
            }
            glDrawArrays(GL_TRIANGLES, 0, numElements);
        }
    }
    
    void ChangeTextureThenDraw(unsigned int iterations,
                               GLsizei numElements,
                               GLuint texture1,
                               GLuint texture2)
    {
        for (unsigned int it = 0; it < iterations; it++)
        {
            glBindTexture(GL_TEXTURE_2D, texture1);
            glDrawArrays(GL_TRIANGLES, 0, numElements);
    
            glBindTexture(GL_TEXTURE_2D, texture2);
            glDrawArrays(GL_TRIANGLES, 0, numElements);
        }
    }
    
    void ChangeProgramThenDraw(unsigned int iterations,
                               GLsizei numElements,
                               GLuint program1,
                               GLuint program2)
    {
        for (unsigned int it = 0; it < iterations; it++)
        {
            glUseProgram(program1);
            glDrawArrays(GL_TRIANGLES, 0, numElements);
    
            glUseProgram(program2);
            glDrawArrays(GL_TRIANGLES, 0, numElements);
        }
    }
    
    void CycleVertexBufferThenDraw(unsigned int iterations,
                                   GLsizei numElements,
                                   const std::vector<GLuint> &vbos,
                                   size_t *currentVBO)
    {
        for (unsigned int it = 0; it < iterations; it++)
        {
            GLuint vbo = vbos[*currentVBO];
            glBindBuffer(GL_ARRAY_BUFFER, vbo);
            glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
            glDrawArrays(GL_TRIANGLES, 0, numElements);
            *currentVBO = (*currentVBO + 1) % vbos.size();
        }
    }
    
    void ChangeScissorThenDraw(unsigned int iterations,
                               GLsizei numElements,
                               unsigned int windowWidth,
                               unsigned int windowHeight)
    {
        // Change scissor as such:
        //
        // - Start with a narrow vertical bar:
        //
        //           Scissor
        //              |
        //              V
        //       +-----+-+-----+
        //       |     | |     | <-- Window
        //       |     | |     |
        //       |     | |     |
        //       |     | |     |
        //       |     | |     |
        //       |     | |     |
        //       +-----+-+-----+
        //
        // - Gradually reduce height and increase width, to end up with a narrow horizontal bar:
        //
        //       +-------------+
        //       |             |
        //       |             |
        //       +-------------+ <-- Scissor
        //       +-------------+
        //       |             |
        //       |             |
        //       +-------------+
        //
        // - If more iterations left, restart, but shift the initial bar left to cover more area:
        //
        //       +---+-+-------+          +-------------+
        //       |   | |       |          |             |
        //       |   | |       |          +-------------+
        //       |   | |       |   --->   |             |
        //       |   | |       |          |             |
        //       |   | |       |          +-------------+
        //       |   | |       |          |             |
        //       +---+-+-------+          +-------------+
        //
        //       +-+-+---------+          +-------------+
        //       | | |         |          +-------------+
        //       | | |         |          |             |
        //       | | |         |   --->   |             |
        //       | | |         |          |             |
        //       | | |         |          |             |
        //       | | |         |          +-------------+
        //       +-+-+---------+          +-------------+
    
        glEnable(GL_SCISSOR_TEST);
    
        constexpr unsigned int kScissorStep  = 2;
        unsigned int scissorX                = windowWidth / 2 - 1;
        unsigned int scissorY                = 0;
        unsigned int scissorWidth            = 2;
        unsigned int scissorHeight           = windowHeight;
        unsigned int scissorPatternIteration = 0;
    
        for (unsigned int it = 0; it < iterations; it++)
        {
            glScissor(scissorX, scissorY, scissorWidth, scissorHeight);
            glDrawArrays(GL_TRIANGLES, 0, numElements);
    
            if (scissorX < kScissorStep || scissorHeight < kScissorStep * 2)
            {
                ++scissorPatternIteration;
                scissorX      = windowWidth / 2 - 1 - scissorPatternIteration * 2;
                scissorY      = 0;
                scissorWidth  = 2;
                scissorHeight = windowHeight;
            }
            else
            {
                scissorX -= kScissorStep;
                scissorY += kScissorStep;
                scissorWidth += kScissorStep * 2;
                scissorHeight -= kScissorStep * 2;
            }
        }
    }
    
    void DrawWithEightTextures(unsigned int iterations,
                               GLsizei numElements,
                               std::vector<GLuint> textures)
    {
        for (unsigned int it = 0; it < iterations; it++)
        {
            for (size_t i = 0; i < textures.size(); ++i)
            {
                glActiveTexture(GL_TEXTURE0 + i);
                size_t index = (it + i) % textures.size();
                glBindTexture(GL_TEXTURE_2D, textures[index]);
            }
    
            glDrawArrays(GL_TRIANGLES, 0, numElements);
        }
    }
    
    void DrawCallPerfBenchmark::drawBenchmark()
    {
        // This workaround fixes a huge queue of graphics commands accumulating on the GL
        // back-end. The GL back-end doesn't have a proper NULL device at the moment.
        // TODO(jmadill): Remove this when/if we ever get a proper OpenGL NULL device.
        const auto &eglParams = GetParam().eglParameters;
        const auto &params    = GetParam();
        GLsizei numElements   = static_cast<GLsizei>(3 * mNumTris);
    
        switch (params.stateChange)
        {
            case StateChange::VertexAttrib:
                ChangeVertexAttribThenDraw<1>(params.iterationsPerStep, numElements, mBuffer1);
                break;
            case StateChange::VertexBuffer:
                ChangeArrayBuffersThenDraw<1>(params.iterationsPerStep, numElements, mBuffer1,
                                              mBuffer2);
                break;
            case StateChange::ManyVertexBuffers:
                ChangeArrayBuffersThenDraw<5>(params.iterationsPerStep, numElements, mBuffer1,
                                              mBuffer2);
                break;
            case StateChange::Texture:
                ChangeTextureThenDraw(params.iterationsPerStep, numElements, mTextures[0],
                                      mTextures[1]);
                break;
            case StateChange::Program:
                ChangeProgramThenDraw(params.iterationsPerStep, numElements, mProgram1, mProgram2);
                break;
            case StateChange::NoChange:
                if (eglParams.deviceType != EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE ||
                    (eglParams.renderer != EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE &&
                     eglParams.renderer != EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE))
                {
                    ClearThenDraw(params.iterationsPerStep, numElements);
                }
                else
                {
                    JustDraw(params.iterationsPerStep, numElements);
                }
                break;
            case StateChange::VertexBufferCycle:
                CycleVertexBufferThenDraw(params.iterationsPerStep, numElements, mVBOPool,
                                          &mCurrentVBO);
                break;
            case StateChange::Scissor:
                ChangeScissorThenDraw(params.iterationsPerStep, numElements, getWindow()->getWidth(),
                                      getWindow()->getHeight());
                break;
            case StateChange::ManyTextureDraw:
                glUseProgram(mProgram3);
                DrawWithEightTextures(params.iterationsPerStep, numElements, mTextures);
                break;
            case StateChange::InvalidEnum:
                ADD_FAILURE() << "Invalid state change.";
                break;
        }
    
        ASSERT_GL_NO_ERROR();
    }
    
    TEST_P(DrawCallPerfBenchmark, Run)
    {
        run();
    }
    
    using namespace params;
    
    DrawArraysPerfParams CombineStateChange(const DrawArraysPerfParams &in, StateChange stateChange)
    {
        DrawArraysPerfParams out = in;
        out.stateChange          = stateChange;
    
        // Crank up iteration count to ensure we cycle through all VBs before a swap.
        if (stateChange == StateChange::VertexBufferCycle)
        {
            out.iterationsPerStep = kCycleVBOPoolSize * 2;
        }
    
        return out;
    }
    
    using P = DrawArraysPerfParams;
    
    std::vector<P> gTestsWithStateChange =
        CombineWithValues({P()}, angle::AllEnums<StateChange>(), CombineStateChange);
    std::vector<P> gTestsWithRenderer =
        CombineWithFuncs(gTestsWithStateChange, {D3D11<P>, GL<P>, Vulkan<P>, WGL<P>});
    std::vector<P> gTestsWithDevice =
        CombineWithFuncs(gTestsWithRenderer, {Passthrough<P>, Offscreen<P>, NullDevice<P>});
    
    ANGLE_INSTANTIATE_TEST_ARRAY(DrawCallPerfBenchmark, gTestsWithDevice);
    
    }  // anonymous namespace