blob: 57e1735a3d2663c1f810cfe88dc4bbf1c0f295af [file] [log] [blame]
// Copyright 2015 Google Inc. 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
//
// 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 "base/threading/simple_thread.h"
#include "base/message_loop.h"
#include "base/run_loop.h"
#include "cobalt/render_tree/resource_provider.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::render_tree::ResourceProvider;
namespace cobalt {
namespace renderer {
namespace {
render_tree::PixelFormat ChoosePixelFormat(
ResourceProvider* resource_provider) {
if (resource_provider->PixelFormatSupported(render_tree::kPixelFormatRGBA8)) {
return render_tree::kPixelFormatRGBA8;
} else if (resource_provider->PixelFormatSupported(
render_tree::kPixelFormatBGRA8)) {
return render_tree::kPixelFormatBGRA8;
} else {
return render_tree::kPixelFormatInvalid;
}
}
// This functor/thread is designed to simply allocate many textures as quickly
// in a row as possible. Many threads should be capable of doing this
// simultaneously.
class CreateImagesThread : public base::SimpleThread {
public:
CreateImagesThread(ResourceProvider* resource_provider,
int num_images_to_create, const math::Size& image_size)
: base::SimpleThread("CreateImages"),
resource_provider_(resource_provider),
num_images_to_create_(num_images_to_create),
image_size_(image_size) {}
void Run() OVERRIDE {
for (int i = 0; i < num_images_to_create_; ++i) {
scoped_ptr<render_tree::ImageData> image_data =
resource_provider_->AllocateImageData(
image_size_, ChoosePixelFormat(resource_provider_),
render_tree::kAlphaFormatPremultiplied);
int num_bytes = image_data->GetDescriptor().pitch_in_bytes *
image_data->GetDescriptor().size.height();
uint8* image_memory = image_data->GetMemory();
for (int i = 0; i < num_bytes; ++i) {
image_memory[i] = 0;
}
resource_provider_->CreateImage(image_data.Pass());
}
}
private:
ResourceProvider* resource_provider_;
int num_images_to_create_;
const math::Size image_size_;
};
// This functor/thread will spawn the given number of CreateImagesThreads,
// execute them, and then wait for them to complete. Upon completion, it will
// call the provided callback function.
class CreateImagesSpawnerThread : public base::SimpleThread {
public:
CreateImagesSpawnerThread(ResourceProvider* resource_provider,
int threads_to_create,
int images_to_create_per_thread,
const math::Size& image_size,
base::Closure finished_callback)
: base::SimpleThread("CreateImagesSpawner"),
resource_provider_(resource_provider),
threads_to_create_(threads_to_create),
images_to_create_per_thread_(images_to_create_per_thread),
image_size_(image_size),
finished_callback_(finished_callback) {}
void Run() OVERRIDE {
// First, create our threads.
typedef std::vector<CreateImagesThread*> ThreadVector;
ThreadVector image_creating_threads;
for (int i = 0; i < threads_to_create_; ++i) {
image_creating_threads.push_back(new CreateImagesThread(
resource_provider_, images_to_create_per_thread_, image_size_));
}
// Second, tell them all to begin executing at the same time.
for (ThreadVector::iterator iter = image_creating_threads.begin();
iter != image_creating_threads.end(); ++iter) {
(*iter)->Start();
}
// Thirdly, wait for them to all finish executing and then destroy them.
for (ThreadVector::iterator iter = image_creating_threads.begin();
iter != image_creating_threads.end(); ++iter) {
(*iter)->Join();
delete *iter;
}
finished_callback_.Run();
}
private:
ResourceProvider* resource_provider_;
int threads_to_create_;
int images_to_create_per_thread_;
const math::Size image_size_;
base::Closure finished_callback_;
};
} // namespace
// The following test ensures that any thread can successfully create render
// tree images both at the same time and also while a graphics frame is started.
// This might be a problem in an OpenGL implementation for instance if we
// are naively setting ourselves as the current OpenGL context in order to
// create a texture, in which case another thread holding the context would
// be upset that it was taken away. This test is essentially attempting to
// cause a crash or DCHECK() to be hit, if the test executes without crashing,
// everything is okay.
TEST(ResourceProviderTest, TexturesCanBeCreatedFromSecondaryThread) {
scoped_ptr<backend::GraphicsSystem> graphics_system =
backend::CreateDefaultGraphicsSystem();
scoped_ptr<backend::GraphicsContext> graphics_context =
graphics_system->CreateGraphicsContext();
// When creating images from another thread, the rasterizer must be
// constructed from a thread with a message loop. This message loop will
// be where all functions that access backend graphics resources are run.
MessageLoop message_loop(MessageLoop::TYPE_DEFAULT);
base::RunLoop run_loop;
// Create the rasterizer using the platform default RenderModule options.
RendererModule::Options render_module_options;
scoped_ptr<rasterizer::Rasterizer> rasterizer =
render_module_options.create_rasterizer_function.Run(
graphics_context.get(), render_module_options);
// Create a dummy offscreen surface so that we can have a target when we start
// a frame with the graphics context.
const math::Size kDummySurfaceDimensions(1, 1);
scoped_refptr<backend::RenderTarget> dummy_output_surface =
graphics_context->CreateOffscreenRenderTarget(kDummySurfaceDimensions);
// Now that we're inside of a new frame, create images from a separate
// thread. This should be perfectly legal and cause no problems.
const int kNumThreads = 20;
const int kNumImagesCreatedPerThread = 300;
// Create a thread to create other threads that will create images.
CreateImagesSpawnerThread spawner_thread(
rasterizer->GetResourceProvider(), kNumThreads,
kNumImagesCreatedPerThread, math::Size(1, 1),
base::Bind(&MessageLoop::PostTask, base::Unretained(&message_loop),
FROM_HERE, run_loop.QuitClosure()));
spawner_thread.Start();
// Run our message loop to process backend image creation/destruction
// requests.
run_loop.Run();
spawner_thread.Join();
}
// If textures never get passed to the renderer, then both allocating and
// releasing them should happen instantly. In other words, this test ensures
// that we don't have a problem where texture allocations are instantaneous
// but texture deallocations are delayed. This might be a problem if the
// video decoder is tracking its video frame texture memory usage to maintain
// up to N textures in memory at a time. If it skips some frames, it might
// release them and then expect that it can allocate N more, but if those
// released frames have a delay on their release, we may have many more than
// N textures allocated at a time.
TEST(ResourceProviderTest, ManyTexturesCanBeCreatedAndDestroyedQuickly) {
scoped_ptr<backend::GraphicsSystem> graphics_system =
backend::CreateDefaultGraphicsSystem();
scoped_ptr<backend::GraphicsContext> graphics_context =
graphics_system->CreateGraphicsContext();
// When creating images from another thread, the rasterizer must be
// constructed from a thread with a message loop. This message loop will
// be where all functions that access backend graphics resources are run.
MessageLoop message_loop(MessageLoop::TYPE_DEFAULT);
base::RunLoop run_loop;
// Create the rasterizer using the platform default RenderModule options.
RendererModule::Options render_module_options;
scoped_ptr<rasterizer::Rasterizer> rasterizer =
render_module_options.create_rasterizer_function.Run(
graphics_context.get(), render_module_options);
// Create a dummy offscreen surface so that we can have a target when we start
// a frame with the graphics context.
const math::Size kDummySurfaceDimensions(1, 1);
scoped_refptr<backend::RenderTarget> dummy_output_surface =
graphics_context->CreateOffscreenRenderTarget(kDummySurfaceDimensions);
// Now that we're inside of a new frame, create images from a separate
// thread. This should be perfectly legal and cause no problems.
const int kNumThreads = 2;
const int kNumImagesCreatedPerThread = 500;
// Create a thread to create other threads that will create images.
CreateImagesSpawnerThread spawner_thread(
rasterizer->GetResourceProvider(), kNumThreads,
kNumImagesCreatedPerThread, math::Size(256, 256),
base::Bind(&MessageLoop::PostTask, base::Unretained(&message_loop),
FROM_HERE, run_loop.QuitClosure()));
spawner_thread.Start();
// Run our message loop to process backend image creation/destruction
// requests.
run_loop.Run();
spawner_thread.Join();
}
} // namespace renderer
} // namespace cobalt