| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * 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. |
| */ |
| |
| #include "AnimationContext.h" |
| #include "RenderNode.h" |
| #include "renderthread/RenderProxy.h" |
| #include "renderthread/RenderTask.h" |
| #include "tests/common/TestContext.h" |
| #include "tests/common/TestScene.h" |
| #include "tests/common/scenes/TestSceneBase.h" |
| |
| #include <benchmark/benchmark.h> |
| #include <gui/Surface.h> |
| #include <log/log.h> |
| #include <ui/PixelFormat.h> |
| |
| using namespace android; |
| using namespace android::uirenderer; |
| using namespace android::uirenderer::renderthread; |
| using namespace android::uirenderer::test; |
| |
| class ContextFactory : public IContextFactory { |
| public: |
| virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { |
| return new AnimationContext(clock); |
| } |
| }; |
| |
| template <class T> |
| class ModifiedMovingAverage { |
| public: |
| explicit ModifiedMovingAverage(int weight) : mWeight(weight) {} |
| |
| T add(T today) { |
| if (!mHasValue) { |
| mAverage = today; |
| } else { |
| mAverage = (((mWeight - 1) * mAverage) + today) / mWeight; |
| } |
| return mAverage; |
| } |
| |
| T average() { return mAverage; } |
| |
| private: |
| bool mHasValue = false; |
| int mWeight; |
| T mAverage; |
| }; |
| |
| void outputBenchmarkReport(const TestScene::Info& info, const TestScene::Options& opts, |
| benchmark::BenchmarkReporter* reporter, RenderProxy* proxy, |
| double durationInS) { |
| using namespace benchmark; |
| |
| struct ReportInfo { |
| int percentile; |
| const char* suffix; |
| }; |
| |
| static std::array<ReportInfo, 4> REPORTS = { |
| ReportInfo{50, "_50th"}, ReportInfo{90, "_90th"}, ReportInfo{95, "_95th"}, |
| ReportInfo{99, "_99th"}, |
| }; |
| |
| // Although a vector is used, it must stay with only a single element |
| // otherwise the BenchmarkReporter will automatically compute |
| // mean and stddev which doesn't make sense for our usage |
| std::vector<BenchmarkReporter::Run> reports; |
| BenchmarkReporter::Run report; |
| report.run_name = info.name; |
| report.iterations = static_cast<int64_t>(opts.count); |
| report.real_accumulated_time = durationInS; |
| report.cpu_accumulated_time = durationInS; |
| report.counters["items_per_second"] = opts.count / durationInS; |
| reports.push_back(report); |
| reporter->ReportRuns(reports); |
| |
| // Pretend the percentiles are single-iteration runs of the test |
| // If rendering offscreen skip this as it's fps that's more interesting |
| // in that test case than percentiles. |
| if (!opts.renderOffscreen) { |
| for (auto& ri : REPORTS) { |
| reports[0].run_name = info.name; |
| reports[0].run_name += ri.suffix; |
| durationInS = proxy->frameTimePercentile(ri.percentile) / 1000.0; |
| reports[0].real_accumulated_time = durationInS; |
| reports[0].cpu_accumulated_time = durationInS; |
| reports[0].iterations = 1; |
| reports[0].counters["items_per_second"] = 0; |
| reporter->ReportRuns(reports); |
| } |
| } |
| } |
| |
| void run(const TestScene::Info& info, const TestScene::Options& opts, |
| benchmark::BenchmarkReporter* reporter) { |
| // Switch to the real display |
| gDisplay = getBuiltInDisplay(); |
| |
| Properties::forceDrawFrame = true; |
| TestContext testContext; |
| testContext.setRenderOffscreen(opts.renderOffscreen); |
| |
| // create the native surface |
| const int width = gDisplay.w; |
| const int height = gDisplay.h; |
| sp<Surface> surface = testContext.surface(); |
| |
| std::unique_ptr<TestScene> scene(info.createScene(opts)); |
| scene->renderTarget = surface; |
| |
| sp<RenderNode> rootNode = TestUtils::createNode( |
| 0, 0, width, height, [&scene, width, height](RenderProperties& props, Canvas& canvas) { |
| props.setClipToBounds(false); |
| scene->createContent(width, height, canvas); |
| }); |
| |
| ContextFactory factory; |
| std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode.get(), &factory)); |
| proxy->loadSystemProperties(); |
| proxy->initialize(surface); |
| float lightX = width / 2.0; |
| proxy->setup(dp(800.0f), 255 * 0.075, 255 * 0.15); |
| proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)}); |
| |
| // Do a few cold runs then reset the stats so that the caches are all hot |
| int warmupFrameCount = 5; |
| if (opts.renderOffscreen) { |
| // Do a few more warmups to try and boost the clocks up |
| warmupFrameCount = 10; |
| } |
| for (int i = 0; i < warmupFrameCount; i++) { |
| testContext.waitForVsync(); |
| nsecs_t vsync = systemTime(CLOCK_MONOTONIC); |
| UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync); |
| proxy->syncAndDrawFrame(); |
| } |
| |
| proxy->resetProfileInfo(); |
| proxy->fence(); |
| |
| ModifiedMovingAverage<double> avgMs(opts.reportFrametimeWeight); |
| |
| nsecs_t start = systemTime(CLOCK_MONOTONIC); |
| for (int i = 0; i < opts.count; i++) { |
| testContext.waitForVsync(); |
| nsecs_t vsync = systemTime(CLOCK_MONOTONIC); |
| { |
| ATRACE_NAME("UI-Draw Frame"); |
| UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync); |
| scene->doFrame(i); |
| proxy->syncAndDrawFrame(); |
| } |
| if (opts.reportFrametimeWeight) { |
| proxy->fence(); |
| nsecs_t done = systemTime(CLOCK_MONOTONIC); |
| avgMs.add((done - vsync) / 1000000.0); |
| if (i % 10 == 9) { |
| printf("Average frametime %.3fms\n", avgMs.average()); |
| } |
| } |
| } |
| proxy->fence(); |
| nsecs_t end = systemTime(CLOCK_MONOTONIC); |
| |
| if (reporter) { |
| outputBenchmarkReport(info, opts, reporter, proxy.get(), (end - start) / (double)s2ns(1)); |
| } else { |
| proxy->dumpProfileInfo(STDOUT_FILENO, DumpFlags::JankStats); |
| } |
| } |