diff options
| author | 2021-10-12 11:33:30 -0400 | |
|---|---|---|
| committer | 2021-10-28 12:02:29 -0400 | |
| commit | 8fcfa66dbb2d75b8bfeaa5168aebe94b93fd8229 (patch) | |
| tree | 67b7f55d5bfaa1e0563e88d498cda1832dce7b34 /libs | |
| parent | f275116c4bef7d4c63725094af523f1433aed657 (diff) | |
sf-latency: Write a benchmark for RenderEngine
Bug: 193240340
Test: mmma -j frameworks/native/libs/renderengine/bench/
&& adb push out/.../librenderengine_bench
/data/.../librenderengine_bench
&& adb shell /data/.../librenderengine_bench
Write a benchmark using Google-Benchmark to track the performance of
RenderEngine.
RenderEngineBench.cpp:
- Contains the benchmarks to run.
- Write a helper function that times calls to RenderEngine::drawLayers.
- Write a single benchmark that times drawing a mock homescreen plus a
blur layer, using a blur radius used in practice.
Decode the homescreen into a buffer with CPU support, and then copy it
to one without, which better represents actual use.
- Use RenderEngineType to determine which subclass to benchmark. The
only existing benchmark uses SKIA_GL, since we intend to time just
drawLayers, but future benchmarks will want to time threading aspects
with SKIA_GL_THREADED, and we may add more RenderEngineTypes in the
future.
Codec.cpp:
- Write methods for decoding and encoding a buffer.
Flags.cpp:
- Extra flags that can be passed to the executable.
- --save allows encoding the result to a file to verify it looks as
intended.
- parse --help to describe --save and any future flags specific to
RenderEngineBench.
RenderEngineBench.h:
- header file for methods used across cpp files. Use a single common
header since otherwise we would have several small headers.
homescreen.png:
- Mock homescreen to draw in drawLayers
Change-Id: I81a1a8a30a1c20985bbf066d2ba4d5f1fd1f6dc3
Diffstat (limited to 'libs')
| -rw-r--r-- | libs/renderengine/benchmark/Android.bp | 54 | ||||
| -rw-r--r-- | libs/renderengine/benchmark/Codec.cpp | 136 | ||||
| -rw-r--r-- | libs/renderengine/benchmark/Flags.cpp | 48 | ||||
| -rw-r--r-- | libs/renderengine/benchmark/RenderEngineBench.cpp | 252 | ||||
| -rw-r--r-- | libs/renderengine/benchmark/RenderEngineBench.h | 65 | ||||
| -rw-r--r-- | libs/renderengine/benchmark/main.cpp | 32 | ||||
| -rw-r--r-- | libs/renderengine/benchmark/resources/homescreen.png | bin | 0 -> 4907729 bytes |
7 files changed, 587 insertions, 0 deletions
diff --git a/libs/renderengine/benchmark/Android.bp b/libs/renderengine/benchmark/Android.bp new file mode 100644 index 0000000000..5968399544 --- /dev/null +++ b/libs/renderengine/benchmark/Android.bp @@ -0,0 +1,54 @@ +// Copyright 2021 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. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_native_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_native_license"], +} + +cc_benchmark { + name: "librenderengine_bench", + defaults: ["skia_deps", "surfaceflinger_defaults"], + srcs: [ + "main.cpp", + "Codec.cpp", + "Flags.cpp", + "RenderEngineBench.cpp", + ], + static_libs: [ + "librenderengine", + ], + cflags: [ + "-DLOG_TAG=\"RenderEngineBench\"", + ], + + shared_libs: [ + "libbase", + "libcutils", + "libjnigraphics", + "libgui", + "liblog", + "libnativewindow", + "libprocessgroup", + "libsync", + "libui", + "libutils", + ], + + data: [ "resources/*"], +} diff --git a/libs/renderengine/benchmark/Codec.cpp b/libs/renderengine/benchmark/Codec.cpp new file mode 100644 index 0000000000..80e4fc4432 --- /dev/null +++ b/libs/renderengine/benchmark/Codec.cpp @@ -0,0 +1,136 @@ +/* + * Copyright 2021 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 <RenderEngineBench.h> +#include <android/bitmap.h> +#include <android/data_space.h> +#include <android/imagedecoder.h> +#include <log/log.h> +#include <renderengine/ExternalTexture.h> +#include <renderengine/RenderEngine.h> +#include <sys/types.h> + +using namespace android; +using namespace android::renderengine; + +namespace { +struct DecoderDeleter { + void operator()(AImageDecoder* decoder) { AImageDecoder_delete(decoder); } +}; + +using AutoDecoderDeleter = std::unique_ptr<AImageDecoder, DecoderDeleter>; + +bool ok(int aImageDecoderResult, const char* path, const char* method) { + if (aImageDecoderResult == ANDROID_IMAGE_DECODER_SUCCESS) { + return true; + } + + ALOGE("Failed AImageDecoder_%s on '%s' with error '%s'", method, path, + AImageDecoder_resultToString(aImageDecoderResult)); + return false; +} +} // namespace + +namespace renderenginebench { + +void decode(const char* path, const sp<GraphicBuffer>& buffer) { + base::unique_fd fd{open(path, O_RDONLY)}; + if (fd.get() < 0) { + ALOGE("Failed to open %s", path); + return; + } + + AImageDecoder* decoder{nullptr}; + auto result = AImageDecoder_createFromFd(fd.get(), &decoder); + if (!ok(result, path, "createFromFd")) { + return; + } + + AutoDecoderDeleter deleter(decoder); + + LOG_ALWAYS_FATAL_IF(buffer->getWidth() <= 0 || buffer->getHeight() <= 0, + "Impossible buffer size!"); + auto width = static_cast<int32_t>(buffer->getWidth()); + auto height = static_cast<int32_t>(buffer->getHeight()); + result = AImageDecoder_setTargetSize(decoder, width, height); + if (!ok(result, path, "setTargetSize")) { + return; + } + + void* pixels{nullptr}; + int32_t stride{0}; + if (auto status = buffer->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, &pixels, + nullptr /*outBytesPerPixel*/, &stride); + status < 0) { + ALOGE("Failed to lock pixels!"); + return; + } + + result = AImageDecoder_decodeImage(decoder, pixels, static_cast<size_t>(stride), + static_cast<size_t>(stride * height)); + if (auto status = buffer->unlock(); status < 0) { + ALOGE("Failed to unlock pixels!"); + } + + // For the side effect of logging. + (void)ok(result, path, "decodeImage"); +} + +void encodeToJpeg(const char* path, const sp<GraphicBuffer>& buffer) { + base::unique_fd fd{open(path, O_WRONLY | O_CREAT, S_IWUSR)}; + if (fd.get() < 0) { + ALOGE("Failed to open %s", path); + return; + } + + void* pixels{nullptr}; + int32_t stride{0}; + if (auto status = buffer->lock(GRALLOC_USAGE_SW_READ_OFTEN, &pixels, + nullptr /*outBytesPerPixel*/, &stride); + status < 0) { + ALOGE("Failed to lock pixels!"); + return; + } + + AndroidBitmapInfo info{ + .width = buffer->getWidth(), + .height = buffer->getHeight(), + .stride = static_cast<uint32_t>(stride), + .format = ANDROID_BITMAP_FORMAT_RGBA_8888, + .flags = ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE, + }; + int result = AndroidBitmap_compress(&info, ADATASPACE_SRGB, pixels, + ANDROID_BITMAP_COMPRESS_FORMAT_JPEG, 80, &fd, + [](void* fdPtr, const void* data, size_t size) -> bool { + const ssize_t bytesWritten = + write(reinterpret_cast<base::unique_fd*>(fdPtr) + ->get(), + data, size); + return bytesWritten > 0 && + static_cast<size_t>(bytesWritten) == size; + }); + if (result == ANDROID_BITMAP_RESULT_SUCCESS) { + ALOGD("Successfully encoded to '%s'", path); + } else { + ALOGE("Failed to encode to %s with error %d", path, result); + } + + if (auto status = buffer->unlock(); status < 0) { + ALOGE("Failed to unlock pixels!"); + } +} + +} // namespace renderenginebench diff --git a/libs/renderengine/benchmark/Flags.cpp b/libs/renderengine/benchmark/Flags.cpp new file mode 100644 index 0000000000..c5d51563f4 --- /dev/null +++ b/libs/renderengine/benchmark/Flags.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2021 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 <log/log.h> +#include <stdio.h> +#include <string.h> + +namespace { +bool gSave = false; +} + +namespace renderenginebench { + +void parseFlagsForHelp(int argc, char** argv) { + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--help")) { + printf("RenderEngineBench-specific flags:\n"); + printf("[--save]: Save the output to the device to confirm drawing result.\n"); + break; + } + } +} + +void parseFlags(int argc, char** argv) { + for (int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--save")) { + gSave = true; + } + } +} + +bool save() { + return gSave; +} +} // namespace renderenginebench diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp new file mode 100644 index 0000000000..719b855348 --- /dev/null +++ b/libs/renderengine/benchmark/RenderEngineBench.cpp @@ -0,0 +1,252 @@ +/* + * Copyright 2021 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 <RenderEngineBench.h> +#include <android-base/file.h> +#include <benchmark/benchmark.h> +#include <gui/SurfaceComposerClient.h> +#include <log/log.h> +#include <renderengine/ExternalTexture.h> +#include <renderengine/LayerSettings.h> +#include <renderengine/RenderEngine.h> + +#include <mutex> + +using namespace android; +using namespace android::renderengine; + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for Benchmark::Apply +/////////////////////////////////////////////////////////////////////////////// + +std::string RenderEngineTypeName(RenderEngine::RenderEngineType type) { + switch (type) { + case RenderEngine::RenderEngineType::SKIA_GL_THREADED: + return "skiaglthreaded"; + case RenderEngine::RenderEngineType::SKIA_GL: + return "skiagl"; + case RenderEngine::RenderEngineType::GLES: + case RenderEngine::RenderEngineType::THREADED: + LOG_ALWAYS_FATAL("GLESRenderEngine is deprecated - why time it?"); + return "unused"; + } +} + +/** + * Passed (indirectly - see RunSkiaGL) to Benchmark::Apply to create a Benchmark + * which specifies which RenderEngineType it uses. + * + * This simplifies calling ->Arg(type)->Arg(type) and provides strings to make + * it obvious which version is being run. + * + * @param b The benchmark family + * @param type The type of RenderEngine to use. + */ +static void AddRenderEngineType(benchmark::internal::Benchmark* b, + RenderEngine::RenderEngineType type) { + b->Arg(static_cast<int64_t>(type)); + b->ArgName(RenderEngineTypeName(type)); +} + +/** + * Run a benchmark once using SKIA_GL. + */ +static void RunSkiaGL(benchmark::internal::Benchmark* b) { + AddRenderEngineType(b, RenderEngine::RenderEngineType::SKIA_GL); +} + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for calling drawLayers +/////////////////////////////////////////////////////////////////////////////// + +std::pair<uint32_t, uint32_t> getDisplaySize() { + // These will be retrieved from a ui::Size, which stores int32_t, but they will be passed + // to GraphicBuffer, which wants uint32_t. + static uint32_t width, height; + std::once_flag once; + std::call_once(once, []() { + auto surfaceComposerClient = SurfaceComposerClient::getDefault(); + auto displayToken = surfaceComposerClient->getInternalDisplayToken(); + ui::DisplayMode displayMode; + if (surfaceComposerClient->getActiveDisplayMode(displayToken, &displayMode) < 0) { + LOG_ALWAYS_FATAL("Failed to get active display mode!"); + } + auto w = displayMode.resolution.width; + auto h = displayMode.resolution.height; + LOG_ALWAYS_FATAL_IF(w <= 0 || h <= 0, "Invalid display size!"); + width = static_cast<uint32_t>(w); + height = static_cast<uint32_t>(h); + }); + return std::pair<uint32_t, uint32_t>(width, height); +} + +// This value doesn't matter, as it's not read. TODO(b/199918329): Once we remove +// GLESRenderEngine we can remove this, too. +static constexpr const bool kUseFrameBufferCache = false; + +static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::RenderEngineType type) { + auto args = RenderEngineCreationArgs::Builder() + .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888)) + .setImageCacheSize(1) + .setEnableProtectedContext(true) + .setPrecacheToneMapperShaderOnly(false) + .setSupportsBackgroundBlur(true) + .setContextPriority(RenderEngine::ContextPriority::REALTIME) + .setRenderEngineType(type) + .setUseColorManagerment(true) + .build(); + return RenderEngine::create(args); +} + +static std::shared_ptr<ExternalTexture> allocateBuffer(RenderEngine& re, + uint32_t width, + uint32_t height, + uint64_t extraUsageFlags = 0, + std::string name = "output") { + return std::make_shared<ExternalTexture>(new GraphicBuffer(width, height, + HAL_PIXEL_FORMAT_RGBA_8888, 1, + GRALLOC_USAGE_HW_RENDER | + GRALLOC_USAGE_HW_TEXTURE | + extraUsageFlags, + std::move(name)), + re, + ExternalTexture::Usage::READABLE | + ExternalTexture::Usage::WRITEABLE); +} + +static std::shared_ptr<ExternalTexture> copyBuffer(RenderEngine& re, + std::shared_ptr<ExternalTexture> original, + uint64_t extraUsageFlags, std::string name) { + const uint32_t width = original->getBuffer()->getWidth(); + const uint32_t height = original->getBuffer()->getHeight(); + auto texture = allocateBuffer(re, width, height, extraUsageFlags, name); + + const Rect displayRect(0, 0, static_cast<int32_t>(width), static_cast<int32_t>(height)); + DisplaySettings display{ + .physicalDisplay = displayRect, + .clip = displayRect, + .maxLuminance = 500, + }; + + const FloatRect layerRect(0, 0, width, height); + LayerSettings layer{ + .geometry = + Geometry{ + .boundaries = layerRect, + }, + .source = + PixelSource{ + .buffer = + Buffer{ + .buffer = original, + }, + }, + .alpha = half(1.0f), + }; + auto layers = std::vector<LayerSettings>{layer}; + + auto [status, drawFence] = + re.drawLayers(display, layers, texture, kUseFrameBufferCache, base::unique_fd()).get(); + sp<Fence> waitFence = new Fence(std::move(drawFence)); + waitFence->waitForever(LOG_TAG); + return texture; +} + +static void benchDrawLayers(RenderEngine& re, const std::vector<LayerSettings>& layers, + benchmark::State& benchState, const char* saveFileName) { + auto [width, height] = getDisplaySize(); + auto outputBuffer = allocateBuffer(re, width, height); + + const Rect displayRect(0, 0, static_cast<int32_t>(width), static_cast<int32_t>(height)); + DisplaySettings display{ + .physicalDisplay = displayRect, + .clip = displayRect, + .maxLuminance = 500, + }; + + base::unique_fd fence; + for (auto _ : benchState) { + auto [status, drawFence] = + re.drawLayers(display, layers, outputBuffer, kUseFrameBufferCache, std::move(fence)) + .get(); + fence = std::move(drawFence); + } + + if (renderenginebench::save() && saveFileName) { + sp<Fence> waitFence = new Fence(std::move(fence)); + waitFence->waitForever(LOG_TAG); + + // Copy to a CPU-accessible buffer so we can encode it. + outputBuffer = copyBuffer(re, outputBuffer, GRALLOC_USAGE_SW_READ_OFTEN, "to_encode"); + + std::string outFile = base::GetExecutableDirectory(); + outFile.append("/"); + outFile.append(saveFileName); + outFile.append(".jpg"); + renderenginebench::encodeToJpeg(outFile.c_str(), outputBuffer->getBuffer()); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Benchmarks +/////////////////////////////////////////////////////////////////////////////// + +void BM_blur(benchmark::State& benchState) { + auto re = createRenderEngine(static_cast<RenderEngine::RenderEngineType>(benchState.range())); + + // Initially use cpu access so we can decode into it with AImageDecoder. + auto [width, height] = getDisplaySize(); + auto srcBuffer = allocateBuffer(*re, width, height, GRALLOC_USAGE_SW_WRITE_OFTEN, + "decoded_source"); + { + std::string srcImage = base::GetExecutableDirectory(); + srcImage.append("/resources/homescreen.png"); + renderenginebench::decode(srcImage.c_str(), srcBuffer->getBuffer()); + + // Now copy into GPU-only buffer for more realistic timing. + srcBuffer = copyBuffer(*re, srcBuffer, 0, "source"); + } + + const FloatRect layerRect(0, 0, width, height); + LayerSettings layer{ + .geometry = + Geometry{ + .boundaries = layerRect, + }, + .source = + PixelSource{ + .buffer = + Buffer{ + .buffer = srcBuffer, + }, + }, + .alpha = half(1.0f), + }; + LayerSettings blurLayer{ + .geometry = + Geometry{ + .boundaries = layerRect, + }, + .alpha = half(1.0f), + .skipContentDraw = true, + .backgroundBlurRadius = 60, + }; + + auto layers = std::vector<LayerSettings>{layer, blurLayer}; + benchDrawLayers(*re, layers, benchState, "blurred"); +} + +BENCHMARK(BM_blur)->Apply(RunSkiaGL); diff --git a/libs/renderengine/benchmark/RenderEngineBench.h b/libs/renderengine/benchmark/RenderEngineBench.h new file mode 100644 index 0000000000..1a25d77f00 --- /dev/null +++ b/libs/renderengine/benchmark/RenderEngineBench.h @@ -0,0 +1,65 @@ +/* + * Copyright 2021 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 <ui/GraphicBuffer.h> + +using namespace android; + +/** + * Utilities for running benchmarks. + */ +namespace renderenginebench { +/** + * Parse RenderEngineBench-specific flags from the command line. + * + * --save Save the output buffer to a file to verify that it drew as + * expected. + */ +void parseFlags(int argc, char** argv); + +/** + * Parse flags for '--help' + */ +void parseFlagsForHelp(int argc, char** argv); + +/** + * Whether to save the drawing result to a file. + * + * True if --save was used on the command line. + */ +bool save(); + +/** + * Decode the image at 'path' into 'buffer'. + * + * Currently only used for debugging. The image will be scaled to fit the + * buffer if necessary. + * + * This assumes the buffer matches ANDROID_BITMAP_FORMAT_RGBA_8888. + * + * @param path Relative to the directory holding the executable. + */ +void decode(const char* path, const sp<GraphicBuffer>& buffer); + +/** + * Encode the buffer to a jpeg. + * + * This assumes the buffer matches ANDROID_BITMAP_FORMAT_RGBA_8888. + * + * @param path Relative to the directory holding the executable. + */ +void encodeToJpeg(const char* path, const sp<GraphicBuffer>& buffer); +} // namespace renderenginebench diff --git a/libs/renderengine/benchmark/main.cpp b/libs/renderengine/benchmark/main.cpp new file mode 100644 index 0000000000..7a62853c9f --- /dev/null +++ b/libs/renderengine/benchmark/main.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2021 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 <RenderEngineBench.h> +#include <benchmark/benchmark.h> + +int main(int argc, char** argv) { + // Initialize will exit if it sees '--help', so check for it and print info + // about our flags first. + renderenginebench::parseFlagsForHelp(argc, argv); + benchmark::Initialize(&argc, argv); + + // Calling this separately from parseFlagsForHelp prevents collisions with + // google-benchmark's flags, since Initialize will consume and remove flags + // it recognizes. + renderenginebench::parseFlags(argc, argv); + benchmark::RunSpecifiedBenchmarks(); + return 0; +} diff --git a/libs/renderengine/benchmark/resources/homescreen.png b/libs/renderengine/benchmark/resources/homescreen.png Binary files differnew file mode 100644 index 0000000000..997b72d7a3 --- /dev/null +++ b/libs/renderengine/benchmark/resources/homescreen.png |