Edit

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

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2020-05-25 17:00:01
    Hash : 6de7ee52
    Message : Clean up overlay RenderPass count reporting. This fixes the trace perf test to accurately report how many RPs in each frame. Instead of counting the RPs on a flush we now count only on a swap call. This won't work for offscreen surfaces which is fine - the overlay doesn't really have the same use for offscreen rendering. Also ignores the first frame in graph data so we can ignore the first setup frame in the trace tests. Also skips the redundant extra "flush" call that would generate an empty space in the RP graph. Gives a cleaner measurement for optimizing the XFB RP count. Bug: angleproject:4622 Change-Id: I5762c500cdb216700247095984ae62b4f8741602 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2215309 Reviewed-by: Tim Van Patten <timvp@google.com> Commit-Queue: Jamie Madill <jmadill@chromium.org>

  • src/tests/perf_tests/TracePerfTest.cpp
  • //
    // Copyright 2020 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.
    //
    // TracePerf:
    //   Performance test for ANGLE replaying traces.
    //
    
    #include <gtest/gtest.h>
    #include "common/PackedEnums.h"
    #include "common/system_utils.h"
    #include "tests/perf_tests/ANGLEPerfTest.h"
    #include "tests/perf_tests/DrawCallPerfParams.h"
    #include "util/egl_loader_autogen.h"
    #include "util/frame_capture_utils.h"
    #include "util/png_utils.h"
    
    #include "restricted_traces/restricted_traces_autogen.h"
    
    #include <cassert>
    #include <functional>
    #include <sstream>
    
    using namespace angle;
    using namespace egl_platform;
    
    namespace
    {
    void FramebufferChangeCallback(void *userData, GLenum target, GLuint framebuffer);
    
    struct TracePerfParams final : public RenderTestParams
    {
        // Common default options
        TracePerfParams()
        {
            majorVersion = 3;
            minorVersion = 0;
            windowWidth  = 1920;
            windowHeight = 1080;
            trackGpuTime = true;
    
            // Display the frame after every drawBenchmark invocation
            iterationsPerStep = 1;
        }
    
        std::string story() const override
        {
            std::stringstream strstr;
            strstr << RenderTestParams::story() << "_" << kTraceInfos[testID].name;
            return strstr.str();
        }
    
        RestrictedTraceID testID;
    };
    
    std::ostream &operator<<(std::ostream &os, const TracePerfParams &params)
    {
        os << params.backendAndStory().substr(1);
        return os;
    }
    
    class TracePerfTest : public ANGLERenderTest, public ::testing::WithParamInterface<TracePerfParams>
    {
      public:
        TracePerfTest();
    
        void initializeBenchmark() override;
        void destroyBenchmark() override;
        void drawBenchmark() override;
    
        void onFramebufferChange(GLenum target, GLuint framebuffer);
    
        uint32_t mStartFrame;
        uint32_t mEndFrame;
    
        double getHostTimeFromGLTime(GLint64 glTime);
    
      private:
        struct QueryInfo
        {
            GLuint beginTimestampQuery;
            GLuint endTimestampQuery;
            GLuint framebuffer;
        };
    
        struct TimeSample
        {
            GLint64 glTime;
            double hostTime;
        };
    
        void sampleTime();
        void saveScreenshot(const std::string &screenshotName) override;
    
        // For tracking RenderPass/FBO change timing.
        QueryInfo mCurrentQuery = {};
        std::vector<QueryInfo> mRunningQueries;
        std::vector<TimeSample> mTimeline;
    
        std::string mStartingDirectory;
    };
    
    TracePerfTest::TracePerfTest()
        : ANGLERenderTest("TracePerf", GetParam()), mStartFrame(0), mEndFrame(0)
    {
        // TODO(anglebug.com/4533) This fails after the upgrade to the 26.20.100.7870 driver.
        if (IsWindows() && IsIntel() &&
            GetParam().getRenderer() == EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE &&
            GetParam().testID == RestrictedTraceID::manhattan_10)
        {
            mSkipTest = true;
        }
    
        // We already swap in TracePerfTest::drawBenchmark, no need to swap again in the harness.
        disableTestHarnessSwap();
    }
    
    void TracePerfTest::initializeBenchmark()
    {
        const auto &params = GetParam();
    
        mStartingDirectory = angle::GetCWD().value();
    
        // To load the trace data path correctly we set the CWD to the executable dir.
        if (!IsAndroid())
        {
            std::string exeDir = angle::GetExecutableDirectory();
            angle::SetCWD(exeDir.c_str());
        }
    
        const TraceInfo &traceInfo = kTraceInfos[params.testID];
        mStartFrame                = traceInfo.startFrame;
        mEndFrame                  = traceInfo.endFrame;
        SetBinaryDataDecompressCallback(params.testID, DecompressBinaryData);
    
        std::stringstream testDataDirStr;
        testDataDirStr << ANGLE_TRACE_DATA_DIR << "/" << traceInfo.name;
        std::string testDataDir = testDataDirStr.str();
        SetBinaryDataDir(params.testID, testDataDir.c_str());
    
        // Potentially slow. Can load a lot of resources.
        SetupReplay(params.testID);
        glFinish();
    
        ASSERT_TRUE(mEndFrame > mStartFrame);
    
        getWindow()->setVisible(true);
    }
    
    #undef TRACE_TEST_CASE
    
    void TracePerfTest::destroyBenchmark()
    {
        // In order for the next test to load, restore the working directory
        angle::SetCWD(mStartingDirectory.c_str());
    }
    
    void TracePerfTest::sampleTime()
    {
        if (mIsTimestampQueryAvailable)
        {
            GLint64 glTime;
            // glGetInteger64vEXT is exported by newer versions of the timer query extensions.
            // Unfortunately only the core EP is exposed by some desktop drivers (e.g. NVIDIA).
            if (glGetInteger64vEXT)
            {
                glGetInteger64vEXT(GL_TIMESTAMP_EXT, &glTime);
            }
            else
            {
                glGetInteger64v(GL_TIMESTAMP_EXT, &glTime);
            }
            mTimeline.push_back({glTime, angle::GetHostTimeSeconds()});
        }
    }
    
    void TracePerfTest::drawBenchmark()
    {
        // Add a time sample from GL and the host.
        sampleTime();
    
        startGpuTimer();
    
        for (uint32_t frame = mStartFrame; frame < mEndFrame; ++frame)
        {
            char frameName[32];
            sprintf(frameName, "Frame %u", frame);
            beginInternalTraceEvent(frameName);
    
            ReplayFrame(GetParam().testID, frame);
            getGLWindow()->swap();
    
            endInternalTraceEvent(frameName);
        }
    
        ResetReplay(GetParam().testID);
    
        // Process any running queries once per iteration.
        for (size_t queryIndex = 0; queryIndex < mRunningQueries.size();)
        {
            const QueryInfo &query = mRunningQueries[queryIndex];
    
            GLuint endResultAvailable = 0;
            glGetQueryObjectuivEXT(query.endTimestampQuery, GL_QUERY_RESULT_AVAILABLE,
                                   &endResultAvailable);
    
            if (endResultAvailable == GL_TRUE)
            {
                char fboName[32];
                sprintf(fboName, "FBO %u", query.framebuffer);
    
                GLint64 beginTimestamp = 0;
                glGetQueryObjecti64vEXT(query.beginTimestampQuery, GL_QUERY_RESULT, &beginTimestamp);
                glDeleteQueriesEXT(1, &query.beginTimestampQuery);
                double beginHostTime = getHostTimeFromGLTime(beginTimestamp);
                beginGLTraceEvent(fboName, beginHostTime);
    
                GLint64 endTimestamp = 0;
                glGetQueryObjecti64vEXT(query.endTimestampQuery, GL_QUERY_RESULT, &endTimestamp);
                glDeleteQueriesEXT(1, &query.endTimestampQuery);
                double endHostTime = getHostTimeFromGLTime(endTimestamp);
                endGLTraceEvent(fboName, endHostTime);
    
                mRunningQueries.erase(mRunningQueries.begin() + queryIndex);
            }
            else
            {
                queryIndex++;
            }
        }
    
        stopGpuTimer();
    }
    
    // Converts a GL timestamp into a host-side CPU time aligned with "GetHostTimeSeconds".
    // This check is necessary to line up sampled trace events in a consistent timeline.
    // Uses a linear interpolation from a series of samples. We do a blocking call to sample
    // both host and GL time once per swap. We then find the two closest GL timestamps and
    // interpolate the host times between them to compute our result. If we are past the last
    // GL timestamp we sample a new data point pair.
    double TracePerfTest::getHostTimeFromGLTime(GLint64 glTime)
    {
        // Find two samples to do a lerp.
        size_t firstSampleIndex = mTimeline.size() - 1;
        while (firstSampleIndex > 0)
        {
            if (mTimeline[firstSampleIndex].glTime < glTime)
            {
                break;
            }
            firstSampleIndex--;
        }
    
        // Add an extra sample if we're missing an ending sample.
        if (firstSampleIndex == mTimeline.size() - 1)
        {
            sampleTime();
        }
    
        const TimeSample &start = mTimeline[firstSampleIndex];
        const TimeSample &end   = mTimeline[firstSampleIndex + 1];
    
        // Note: we have observed in some odd cases later timestamps producing values that are
        // smaller than preceding timestamps. This bears further investigation.
    
        // Compute the scaling factor for the lerp.
        double glDelta = static_cast<double>(glTime - start.glTime);
        double glRange = static_cast<double>(end.glTime - start.glTime);
        double t       = glDelta / glRange;
    
        // Lerp(t1, t2, t)
        double hostRange = end.hostTime - start.hostTime;
        return mTimeline[firstSampleIndex].hostTime + hostRange * t;
    }
    
    // Callback from the perf tests.
    void TracePerfTest::onFramebufferChange(GLenum target, GLuint framebuffer)
    {
        if (!mIsTimestampQueryAvailable)
            return;
    
        if (target != GL_FRAMEBUFFER && target != GL_DRAW_FRAMEBUFFER)
            return;
    
        // We have at most one active timestamp query at a time. This code will end the current
        // query and immediately start a new one.
        if (mCurrentQuery.beginTimestampQuery != 0)
        {
            glGenQueriesEXT(1, &mCurrentQuery.endTimestampQuery);
            glQueryCounterEXT(mCurrentQuery.endTimestampQuery, GL_TIMESTAMP_EXT);
            mRunningQueries.push_back(mCurrentQuery);
            mCurrentQuery = {};
        }
    
        ASSERT(mCurrentQuery.beginTimestampQuery == 0);
    
        glGenQueriesEXT(1, &mCurrentQuery.beginTimestampQuery);
        glQueryCounterEXT(mCurrentQuery.beginTimestampQuery, GL_TIMESTAMP_EXT);
        mCurrentQuery.framebuffer = framebuffer;
    }
    
    void TracePerfTest::saveScreenshot(const std::string &screenshotName)
    {
        // Render a single frame.
        RestrictedTraceID testID   = GetParam().testID;
        const TraceInfo &traceInfo = kTraceInfos[testID];
        ReplayFrame(testID, traceInfo.startFrame);
    
        // RGBA 4-byte data.
        uint32_t pixelCount = mTestParams.windowWidth * mTestParams.windowHeight;
        std::vector<uint8_t> pixelData(pixelCount * 4);
    
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glReadPixels(0, 0, mTestParams.windowWidth, mTestParams.windowHeight, GL_RGBA, GL_UNSIGNED_BYTE,
                     pixelData.data());
    
        // Convert to RGB and flip y.
        std::vector<uint8_t> rgbData(pixelCount * 3);
        for (EGLint y = 0; y < mTestParams.windowHeight; ++y)
        {
            for (EGLint x = 0; x < mTestParams.windowWidth; ++x)
            {
                EGLint srcPixel = x + y * mTestParams.windowWidth;
                EGLint dstPixel = x + (mTestParams.windowHeight - y - 1) * mTestParams.windowWidth;
                memcpy(&rgbData[dstPixel * 3], &pixelData[srcPixel * 4], 3);
            }
        }
    
        angle::SavePNGRGB(screenshotName.c_str(), "ANGLE Screenshot", mTestParams.windowWidth,
                          mTestParams.windowHeight, rgbData);
    
        // Finish the frame loop.
        for (uint32_t nextFrame = traceInfo.startFrame + 1; nextFrame < traceInfo.endFrame; ++nextFrame)
        {
            ReplayFrame(testID, nextFrame);
        }
        getGLWindow()->swap();
        glFinish();
    }
    
    ANGLE_MAYBE_UNUSED void FramebufferChangeCallback(void *userData, GLenum target, GLuint framebuffer)
    {
        reinterpret_cast<TracePerfTest *>(userData)->onFramebufferChange(target, framebuffer);
    }
    
    TEST_P(TracePerfTest, Run)
    {
        run();
    }
    
    TracePerfParams CombineTestID(const TracePerfParams &in, RestrictedTraceID id)
    {
        TracePerfParams out = in;
        out.testID          = id;
        return out;
    }
    
    using namespace params;
    using P = TracePerfParams;
    
    std::vector<P> gTestsWithID =
        CombineWithValues({P()}, AllEnums<RestrictedTraceID>(), CombineTestID);
    std::vector<P> gTestsWithRenderer = CombineWithFuncs(gTestsWithID, {Vulkan<P>, EGL<P>});
    ANGLE_INSTANTIATE_TEST_ARRAY(TracePerfTest, gTestsWithRenderer);
    
    }  // anonymous namespace