blob: d5ab9da0fd1fa4aefc8a8ffe5e2c753c511fa697 [file] [log] [blame]
// Copyright 2017 The Cobalt Authors. All Rights Reserved.
// 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
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include <vector>
#include "cobalt/render_tree/brush.h"
#include "cobalt/render_tree/composition_node.h"
#include "cobalt/render_tree/filter_node.h"
#include "cobalt/render_tree/image_node.h"
#include "cobalt/render_tree/rect_node.h"
#include "cobalt/renderer/backend/default_graphics_system.h"
#include "cobalt/renderer/backend/graphics_context.h"
#include "cobalt/renderer/backend/graphics_system.h"
#include "cobalt/renderer/backend/render_target.h"
#include "cobalt/renderer/rasterizer/rasterizer.h"
#include "cobalt/renderer/renderer_module.h"
#include "testing/gtest/include/gtest/gtest.h"
using cobalt::math::RectF;
using cobalt::math::Size;
using cobalt::math::SizeF;
using cobalt::math::Vector2dF;
using cobalt::render_tree::Brush;
using cobalt::render_tree::ColorRGBA;
using cobalt::render_tree::CompositionNode;
using cobalt::render_tree::FilterNode;
using cobalt::render_tree::Image;
using cobalt::render_tree::ImageData;
using cobalt::render_tree::ImageNode;
using cobalt::render_tree::Node;
using cobalt::render_tree::OpacityFilter;
using cobalt::render_tree::RectNode;
using cobalt::render_tree::SolidColorBrush;
namespace cobalt {
namespace renderer {
namespace rasterizer {
// The renderer stress tests are intended to simply ensure that no crash occurs.
// They usually create a lot of a certain object type, or attempt to use very
// large surfaces. The final output doesn't really matter, and it is not
// checked, we just don't want to crash. For example, some of the tests
// ensure that we don't crash when we request a rasterization that requires
// very large framebuffers that the rasterizer is not likely to be able to
// allocate.
class StressTest : public testing::Test {
static void SetUpTestCase();
static void TearDownTestCase();
void TestTree(const Size& output_size, scoped_refptr<Node> tree);
render_tree::ResourceProvider* GetResourceProvider() const {
return rasterizer_->GetResourceProvider();
std::unique_ptr<rasterizer::Rasterizer> rasterizer_;
scoped_refptr<backend::RenderTarget> render_target_;
static backend::GraphicsSystem* graphics_system_;
static backend::GraphicsContext* graphics_context_;
StressTest::StressTest() {
// Create the rasterizer using the platform default RenderModule options.
RendererModule::Options render_module_options;
rasterizer_ = render_module_options.create_rasterizer_function.Run(
graphics_context_, render_module_options);
// static
backend::GraphicsSystem* StressTest::graphics_system_ = nullptr;
// static
backend::GraphicsContext* StressTest::graphics_context_ = nullptr;
// static
void StressTest::SetUpTestCase() {
graphics_system_ = backend::CreateDefaultGraphicsSystem().release();
graphics_context_ = graphics_system_->CreateGraphicsContext().release();
// static
void StressTest::TearDownTestCase() {
delete graphics_context_;
graphics_context_ = nullptr;
delete graphics_system_;
graphics_system_ = nullptr;
void StressTest::TestTree(const Size& output_size, scoped_refptr<Node> tree) {
// Reuse render targets to avoid some rasterizers from thrashing their
// render target cache.
if (!render_target_ || render_target_->GetSize() != output_size) {
render_target_ =
if (!render_target_) {
<< "Failed to create render target, no rasterization will take place.";
} else {
rasterizer::Rasterizer::Options rasterizer_options;
rasterizer_options.flags = rasterizer::Rasterizer::kSubmitFlags_Clear;
rasterizer_->Submit(tree, render_target_, rasterizer_options);
// Test that we can create a large framebuffer, but not so large that it
// will fail on all platforms.
TEST_F(StressTest, LargeFramebuffer) {
Size kLargeFramebufferSize(8000, 8000);
new RectNode(RectF(kLargeFramebufferSize),
std::unique_ptr<Brush>(new SolidColorBrush(
ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f)))));
// Test that we can create a very large framebuffer that may fail to create
// on most platforms.
TEST_F(StressTest, VeryLargeFramebuffer) {
Size kLargeFramebufferSize(20000, 20000);
new RectNode(RectF(kLargeFramebufferSize),
std::unique_ptr<Brush>(new SolidColorBrush(
ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f)))));
namespace {
// Creates a composition of |cascade_amount| render trees layed out in a
// cascade on top of each other.
scoped_refptr<Node> CreateCascadedRenderTrees(Node* node, int cascade_amount,
float offset_amount) {
CompositionNode::Builder composition_builder;
for (int i = 0; i < cascade_amount; ++i) {
composition_builder.AddChild(new CompositionNode(
node, Vector2dF(i * offset_amount, i * offset_amount)));
return new CompositionNode(std::move(composition_builder));
// Creates and returns a render tree with |num_layers| opacity objects overlaid
// on top of themselves, each with the specified size. Each opacity layer
// contains a set of 3 rectangles, with an opacity filter applied on top of
// all 3 of them.
scoped_refptr<Node> CreateOpacityLayers(int num_layers, const Size& size) {
const float kCascadeOffset = 25.0f;
const int kNumCascadedRects = 3;
int rect_size_inset = (kNumCascadedRects - 1) * kCascadeOffset;
Size rect_size(size.width() - rect_size_inset,
size.height() - rect_size_inset);
FilterNode* opacity_layer = new FilterNode(
new RectNode(RectF(rect_size),
std::unique_ptr<Brush>(new SolidColorBrush(
ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f)))),
kNumCascadedRects, kCascadeOffset));
return CreateCascadedRenderTrees(opacity_layer, num_layers, 0.0f);
} // namespace
// Test that we can create many (8^3) medium-sized opacity layers (which, in
// most rasterizers, imply the creation of offscreen surfaces).
TEST_F(StressTest, ManyOpacityLayers) {
Size kFramebufferSize(1920, 1080);
TestTree(kFramebufferSize, CreateOpacityLayers(250, Size(200, 200)));
TEST_F(StressTest, FewLargeOpacityLayers) {
Size kFramebufferSize(1920, 1080);
TestTree(kFramebufferSize, CreateOpacityLayers(50, kFramebufferSize));
TEST_F(StressTest, FewVeryLargeOpacityLayers) {
Size kFramebufferSize(1920, 1080);
TestTree(kFramebufferSize, CreateOpacityLayers(50, Size(9000, 9000)));
TEST_F(StressTest, TooManyTextures) {
// Try to use all the available texture memory in order to test failure to
// allocate a texture.
const int kTextureMemoryMb = 2 * 1024;
const Size kFramebufferSize(1920, 1080);
const Size kTextureSize(2048, 2048);
const int kTextureSizeMb = kTextureSize.GetArea() * 4 / (1024 * 1024);
const int kNumTextures = kTextureMemoryMb / kTextureSizeMb + 1;
render_tree::PixelFormat pixel_format = render_tree::kPixelFormatRGBA8;
if (!GetResourceProvider()->PixelFormatSupported(pixel_format)) {
pixel_format = render_tree::kPixelFormatBGRA8;
std::vector<scoped_refptr<Image>> images;
for (int i = 0; i < kNumTextures; ++i) {
// On most platforms, AllocateImageData allocates CPU memory and
// CreateImage will allocate GPU memory (and release the CPU memory) when
// the image is needed. To exercise running out of GPU memory, try
// rendering each new image so the CPU allocation can be released before
// the next image is allocated.
std::unique_ptr<ImageData> image_data =
kTextureSize, pixel_format, render_tree::kAlphaFormatOpaque);
if (!image_data) {
TestTree(kFramebufferSize, new ImageNode(images.back()));
} // namespace rasterizer
} // namespace renderer
} // namespace cobalt