blob: 59958a5165fc7801ed0d0d0a3aa3088b050e2562 [file] [log] [blame]
// Copyright 2017 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 "cobalt/renderer/rasterizer/egl/hardware_rasterizer.h"
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include "base/debug/trace_event.h"
#include "base/memory/scoped_vector.h"
#include "base/threading/thread_checker.h"
#include "cobalt/math/size.h"
#include "cobalt/renderer/backend/egl/framebuffer.h"
#include "cobalt/renderer/backend/egl/graphics_context.h"
#include "cobalt/renderer/backend/egl/graphics_system.h"
#include "cobalt/renderer/backend/egl/texture.h"
#include "cobalt/renderer/backend/egl/utils.h"
#include "cobalt/renderer/frame_rate_throttler.h"
#include "cobalt/renderer/rasterizer/egl/draw_object_manager.h"
#include "cobalt/renderer/rasterizer/egl/graphics_state.h"
#include "cobalt/renderer/rasterizer/egl/rect_allocator.h"
#include "cobalt/renderer/rasterizer/egl/render_tree_node_visitor.h"
#include "cobalt/renderer/rasterizer/egl/shader_program_manager.h"
#include "cobalt/renderer/rasterizer/skia/hardware_rasterizer.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/core/SkSurfaceProps.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "third_party/skia/include/gpu/GrRenderTarget.h"
#include "third_party/skia/include/gpu/GrTypes.h"
namespace cobalt {
namespace renderer {
namespace rasterizer {
namespace egl {
namespace {
int32_t NextPowerOf2(int32_t num) {
// Return the smallest power of 2 that is greater than or equal to num.
// This flips on all bits <= num, then num+1 will be the next power of 2.
--num;
num |= num >> 1;
num |= num >> 2;
num |= num >> 4;
num |= num >> 8;
num |= num >> 16;
return num + 1;
}
bool IsPowerOf2(int32_t num) {
return (num & (num - 1)) == 0;
}
} // namespace
class HardwareRasterizer::Impl {
public:
explicit Impl(backend::GraphicsContext* graphics_context,
int skia_cache_size_in_bytes,
int scratch_surface_cache_size_in_bytes,
int surface_cache_size_in_bytes);
~Impl();
void Submit(const scoped_refptr<render_tree::Node>& render_tree,
const scoped_refptr<backend::RenderTarget>& render_target,
const Options& options);
void SubmitToFallbackRasterizer(
const scoped_refptr<render_tree::Node>& render_tree,
const math::RectF& viewport,
const backend::TextureEGL** out_texture,
math::Matrix3F* out_texcoord_transform);
render_tree::ResourceProvider* GetResourceProvider() {
return fallback_rasterizer_->GetResourceProvider();
}
private:
// Use an atlas for offscreen targets.
struct OffscreenAtlas {
explicit OffscreenAtlas(const math::Size& size) : allocator(size) {}
RectAllocator allocator;
scoped_ptr<backend::FramebufferEGL> framebuffer;
SkAutoTUnref<SkSurface> skia_surface;
};
GrContext* GetFallbackContext() {
return fallback_rasterizer_->GetGrContext();
}
void RasterizeTree(const scoped_refptr<render_tree::Node>& render_tree);
void AllocateOffscreenTarget(const math::SizeF& size,
OffscreenAtlas** out_atlas, math::RectF* out_target_rect);
OffscreenAtlas* AddOffscreenAtlas(const math::Size& size);
scoped_ptr<skia::HardwareRasterizer> fallback_rasterizer_;
scoped_ptr<GraphicsState> graphics_state_;
scoped_ptr<ShaderProgramManager> shader_program_manager_;
backend::GraphicsContextEGL* graphics_context_;
FrameRateThrottler frame_rate_throttler_;
base::ThreadChecker thread_checker_;
typedef ScopedVector<OffscreenAtlas> OffscreenAtlasList;
OffscreenAtlasList offscreen_atlases_;
// Size of the smallest offscreen target atlas that can hold all offscreen
// targets requested this frame.
math::Size offscreen_atlas_size_;
// Align offscreen targets to a particular size to more efficiently use the
// offscreen target atlas. Use a power of 2 for the alignment so that a bit
// mask can be used for the alignment calculation.
math::Size offscreen_target_size_mask_;
};
HardwareRasterizer::Impl::Impl(
backend::GraphicsContext* graphics_context,
int skia_cache_size_in_bytes,
int scratch_surface_cache_size_in_bytes,
int surface_cache_size_in_bytes)
: fallback_rasterizer_(new skia::HardwareRasterizer(
graphics_context,
skia_cache_size_in_bytes,
scratch_surface_cache_size_in_bytes,
surface_cache_size_in_bytes)),
graphics_context_(
base::polymorphic_downcast<backend::GraphicsContextEGL*>(
graphics_context)),
offscreen_atlas_size_(0, 0),
offscreen_target_size_mask_(0, 0) {
backend::GraphicsContextEGL::ScopedMakeCurrent scoped_make_current(
graphics_context_);
graphics_state_.reset(new GraphicsState());
shader_program_manager_.reset(new ShaderProgramManager());
}
HardwareRasterizer::Impl::~Impl() {
backend::GraphicsContextEGL::ScopedMakeCurrent scoped_make_current(
graphics_context_);
GL_CALL(glFinish());
offscreen_atlases_.clear();
shader_program_manager_.reset();
graphics_state_.reset();
}
void HardwareRasterizer::Impl::Submit(
const scoped_refptr<render_tree::Node>& render_tree,
const scoped_refptr<backend::RenderTarget>& render_target,
const Options& options) {
DCHECK(thread_checker_.CalledOnValidThread());
backend::RenderTargetEGL* render_target_egl =
base::polymorphic_downcast<backend::RenderTargetEGL*>(
render_target.get());
backend::GraphicsContextEGL::ScopedMakeCurrent scoped_make_current(
graphics_context_, render_target_egl);
fallback_rasterizer_->AdvanceFrame();
const math::Size& target_size = render_target->GetSize();
graphics_state_->SetClipAdjustment(target_size);
graphics_state_->Viewport(0, 0, target_size.width(), target_size.height());
// Update only the dirty pixels if the render target contents are preserved
// between frames.
if (options.dirty && render_target_egl->ContentWasPreservedAfterSwap()) {
graphics_state_->Scissor(options.dirty->x(), options.dirty->y(),
options.dirty->width(), options.dirty->height());
} else {
graphics_state_->Scissor(0, 0, target_size.width(), target_size.height());
}
// Set initial characteristics for offscreen target handling.
if (offscreen_atlas_size_.IsEmpty()) {
if (target_size.width() >= 64 && target_size.height() >= 64) {
offscreen_atlas_size_.SetSize(
NextPowerOf2(target_size.width() / 16),
NextPowerOf2(target_size.height() / 16));
offscreen_target_size_mask_.SetSize(
NextPowerOf2(target_size.width() / 64) - 1,
NextPowerOf2(target_size.height() / 64) - 1);
} else {
offscreen_atlas_size_.SetSize(16, 16);
offscreen_target_size_mask_.SetSize(0, 0);
}
}
// Keep only the largest offscreen target atlas.
while (offscreen_atlases_.size() > 1) {
offscreen_atlases_.erase(offscreen_atlases_.begin());
}
if (!offscreen_atlases_.empty()) {
offscreen_atlases_.back()->allocator.Reset();
offscreen_atlases_.back()->skia_surface->getCanvas()->clear(
SK_ColorTRANSPARENT);
}
RasterizeTree(render_tree);
frame_rate_throttler_.EndInterval();
graphics_context_->SwapBuffers(render_target_egl);
frame_rate_throttler_.BeginInterval();
}
void HardwareRasterizer::Impl::SubmitToFallbackRasterizer(
const scoped_refptr<render_tree::Node>& render_tree,
const math::RectF& viewport,
const backend::TextureEGL** out_texture,
math::Matrix3F* out_texcoord_transform) {
DCHECK(thread_checker_.CalledOnValidThread());
TRACE_EVENT0("cobalt::renderer", "SubmitToFallbackRasterizer");
math::RectF target_rect(0, 0, 0, 0);
OffscreenAtlas* atlas = NULL;
AllocateOffscreenTarget(viewport.size(), &atlas, &target_rect);
backend::FramebufferEGL* framebuffer = atlas->framebuffer.get();
SkCanvas* canvas = atlas->skia_surface->getCanvas();
// Use skia to rasterize to the allocated offscreen target.
canvas->save();
canvas->clipRect(SkRect::MakeXYWH(
target_rect.x(), target_rect.y(),
viewport.width() + 0.5f, viewport.height() + 0.5f));
canvas->translate(viewport.x() + target_rect.x(),
viewport.y() + target_rect.y());
fallback_rasterizer_->SubmitOffscreen(render_tree, canvas);
canvas->restore();
float scale_x = 1.0f / framebuffer->GetSize().width();
float scale_y = 1.0f / framebuffer->GetSize().height();
*out_texcoord_transform = math::Matrix3F::FromValues(
viewport.width() * scale_x, 0, target_rect.x() * scale_x,
0, viewport.height() * scale_y, target_rect.y() * scale_y,
0, 0, 1);
*out_texture = framebuffer->GetColorTexture();
}
void HardwareRasterizer::Impl::RasterizeTree(
const scoped_refptr<render_tree::Node>& render_tree) {
DrawObjectManager draw_object_manager;
RenderTreeNodeVisitor::FallbackRasterizeFunction fallback_rasterize =
base::Bind(&HardwareRasterizer::Impl::SubmitToFallbackRasterizer,
base::Unretained(this));
RenderTreeNodeVisitor visitor(graphics_state_.get(),
&draw_object_manager,
&fallback_rasterize);
// Traverse the render tree to populate the draw object manager.
{
TRACE_EVENT0("cobalt::renderer", "VisitRenderTree");
render_tree->Accept(&visitor);
}
graphics_state_->BeginFrame();
// Rasterize to offscreen targets using skia.
{
TRACE_EVENT0("cobalt::renderer", "OffscreenRasterize");
// Reset the skia graphics context since the egl rasterizer dirtied it.
GetFallbackContext()->resetContext();
draw_object_manager.ExecuteOffscreenRasterize(graphics_state_.get(),
shader_program_manager_.get());
{
TRACE_EVENT0("cobalt::renderer", "Skia Flush");
for (OffscreenAtlasList::iterator iter = offscreen_atlases_.begin();
iter != offscreen_atlases_.end(); ++iter) {
(*iter)->skia_surface->getCanvas()->flush();
}
}
// Reset the egl graphics state since skia dirtied it.
graphics_state_->SetDirty();
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, 0));
}
graphics_state_->Clear();
{
TRACE_EVENT0("cobalt::renderer", "OnscreenUpdateVertexBuffer");
draw_object_manager.ExecuteOnscreenUpdateVertexBuffer(graphics_state_.get(),
shader_program_manager_.get());
graphics_state_->UpdateVertexData();
}
{
TRACE_EVENT0("cobalt::renderer", "OnscreenRasterize");
draw_object_manager.ExecuteOnscreenRasterize(graphics_state_.get(),
shader_program_manager_.get());
}
graphics_state_->EndFrame();
}
void HardwareRasterizer::Impl::AllocateOffscreenTarget(
const math::SizeF& size,
OffscreenAtlas** out_atlas, math::RectF* out_target_rect) {
// Get an offscreen target for rendering. Align up the requested target size
// to improve usage of the atlas (since more requests will have the same
// aligned width or height).
DCHECK(IsPowerOf2(offscreen_target_size_mask_.width() + 1));
DCHECK(IsPowerOf2(offscreen_target_size_mask_.height() + 1));
math::Size target_size(
(static_cast<int>(size.width() + 0.5f) +
offscreen_target_size_mask_.width()) &
~offscreen_target_size_mask_.width(),
(static_cast<int>(size.height() + 0.5f) +
offscreen_target_size_mask_.height()) &
~offscreen_target_size_mask_.height());
math::Rect target_rect(0, 0, 0, 0);
OffscreenAtlas* atlas = NULL;
// See if there's room in the most recently created offscreen target atlas.
// Don't search any other atlases since we want to quickly find an offscreen
// atlas size large enough to hold all offscreen targets needed in a frame.
if (!offscreen_atlases_.empty()) {
atlas = offscreen_atlases_.back();
target_rect = atlas->allocator.Allocate(target_size);
}
if (target_rect.IsEmpty()) {
// Create a new offscreen atlas, bigger than the previous, so that
// eventually only one offscreen atlas is needed per frame.
bool grew = false;
if (offscreen_atlas_size_.width() < target_size.width()) {
offscreen_atlas_size_.set_width(NextPowerOf2(target_size.width()));
grew = true;
}
if (offscreen_atlas_size_.height() < target_size.height()) {
offscreen_atlas_size_.set_height(NextPowerOf2(target_size.height()));
grew = true;
}
if (!grew) {
// Grow the offscreen atlas while keeping it square-ish.
if (offscreen_atlas_size_.width() <= offscreen_atlas_size_.height()) {
offscreen_atlas_size_.set_width(offscreen_atlas_size_.width() * 2);
} else {
offscreen_atlas_size_.set_height(offscreen_atlas_size_.height() * 2);
}
}
atlas = AddOffscreenAtlas(offscreen_atlas_size_);
target_rect = atlas->allocator.Allocate(target_size);
}
DCHECK(!target_rect.IsEmpty());
*out_atlas = atlas;
*out_target_rect = target_rect;
}
HardwareRasterizer::Impl::OffscreenAtlas*
HardwareRasterizer::Impl::AddOffscreenAtlas(const math::Size& size) {
OffscreenAtlas* atlas = new OffscreenAtlas(offscreen_atlas_size_);
offscreen_atlases_.push_back(atlas);
// Create a new framebuffer.
atlas->framebuffer.reset(new backend::FramebufferEGL(
graphics_context_, offscreen_atlas_size_, GL_RGBA, GL_NONE));
// Wrap the framebuffer as a skia surface.
GrBackendRenderTargetDesc skia_desc;
skia_desc.fWidth = offscreen_atlas_size_.width();
skia_desc.fHeight = offscreen_atlas_size_.height();
skia_desc.fConfig = kRGBA_8888_GrPixelConfig;
skia_desc.fOrigin = kTopLeft_GrSurfaceOrigin;
skia_desc.fSampleCnt = 0;
skia_desc.fStencilBits = 0;
skia_desc.fRenderTargetHandle = atlas->framebuffer->gl_handle();
SkAutoTUnref<GrRenderTarget> skia_render_target(
GetFallbackContext()->wrapBackendRenderTarget(skia_desc));
SkSurfaceProps skia_surface_props(
SkSurfaceProps::kUseDistanceFieldFonts_Flag,
SkSurfaceProps::kLegacyFontHost_InitType);
atlas->skia_surface.reset(SkSurface::NewRenderTargetDirect(
skia_render_target, &skia_surface_props));
atlas->skia_surface->getCanvas()->clear(SK_ColorTRANSPARENT);
return atlas;
}
HardwareRasterizer::HardwareRasterizer(
backend::GraphicsContext* graphics_context,
int skia_cache_size_in_bytes,
int scratch_surface_cache_size_in_bytes,
int surface_cache_size_in_bytes)
: impl_(new Impl(graphics_context,
skia_cache_size_in_bytes,
scratch_surface_cache_size_in_bytes,
surface_cache_size_in_bytes)) {
}
void HardwareRasterizer::Submit(
const scoped_refptr<render_tree::Node>& render_tree,
const scoped_refptr<backend::RenderTarget>& render_target,
const Options& options) {
TRACE_EVENT0("cobalt::renderer", "HardwareRasterizer::Submit");
impl_->Submit(render_tree, render_target, options);
}
render_tree::ResourceProvider* HardwareRasterizer::GetResourceProvider() {
return impl_->GetResourceProvider();
}
} // namespace egl
} // namespace rasterizer
} // namespace renderer
} // namespace cobalt