| // Copyright 2016 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/skia/surface_cache_delegate.h" |
| |
| #include <algorithm> |
| #include <string> |
| |
| #include "base/debug/trace_event.h" |
| #include "cobalt/base/polymorphic_downcast.h" |
| #include "cobalt/math/transform_2d.h" |
| #include "cobalt/math/vector2d_f.h" |
| #include "cobalt/math/vector3d_f.h" |
| #include "cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.h" |
| |
| namespace cobalt { |
| namespace renderer { |
| namespace rasterizer { |
| namespace skia { |
| |
| SurfaceCacheDelegate::SurfaceCacheDelegate( |
| const CreateSkSurfaceFunction& create_sk_surface_function, |
| const math::Size& max_surface_size) |
| : create_sk_surface_function_(create_sk_surface_function), |
| max_surface_size_(max_surface_size), |
| draw_state_(NULL) |
| #if defined(ENABLE_DEBUG_CONSOLE) |
| , |
| toggle_cache_highlights_(false), |
| toggle_cache_highlights_command_handler_( |
| "toggle_cache_highlights", |
| base::Bind(&SurfaceCacheDelegate::OnToggleCacheHighlights, |
| base::Unretained(this)), |
| "Toggles cache highlights showing render tree nodes that are cached.", |
| "Will replace render tree node images with translucent red " |
| "rectangles so that cached nodes can be visualized on screen.") |
| #endif |
| { |
| } |
| |
| void SurfaceCacheDelegate::UpdateCanvasScale() { |
| math::Matrix3F total_matrix = |
| SkiaMatrixToCobalt(draw_state_->render_target->getTotalMatrix()); |
| math::Vector3dF column_0(total_matrix.column(0)); |
| math::Vector3dF column_1(total_matrix.column(1)); |
| scale_ = math::Vector2dF(column_0.Length(), column_1.Length()); |
| } |
| |
| #if defined(ENABLE_DEBUG_CONSOLE) |
| void SurfaceCacheDelegate::OnToggleCacheHighlights(const std::string& message) { |
| toggle_cache_highlights_ = !toggle_cache_highlights_; |
| } |
| #endif |
| |
| void SurfaceCacheDelegate::ApplySurface( |
| common::SurfaceCache::CachedSurface* surface) { |
| TRACE_EVENT0("cobalt::renderer", "SurfaceCacheDelegate::ApplySurface()"); |
| CachedSurface* skia_surface = |
| base::polymorphic_downcast<CachedSurface*>(surface); |
| |
| if (!skia_surface->image()) { |
| return; |
| } |
| |
| draw_state_->render_target->save(); |
| SkMatrix total_matrix = draw_state_->render_target->getTotalMatrix(); |
| // We "preScale()" the "post scale" because skia uses "pre" to mean that the |
| // transform will be applied before the other transforms, whereas |
| // common::OffscreenRenderCoordinateMapping() uses "post" to mean that the |
| // transform matrix should be post-multiplied by the existing matrix. |
| total_matrix.preScale(skia_surface->output_post_scale().x(), |
| skia_surface->output_post_scale().y()); |
| total_matrix.postTranslate(skia_surface->output_pre_translate().x(), |
| skia_surface->output_pre_translate().y()); |
| draw_state_->render_target->setMatrix(total_matrix); |
| |
| SkPaint paint; |
| paint.setFilterLevel(SkPaint::kLow_FilterLevel); |
| if (draw_state_->opacity < 1.0f) { |
| paint.setAlpha(draw_state_->opacity * 255); |
| } |
| |
| SkRect dest_rect = SkRect::MakeXYWH(skia_surface->output_bounds().x(), |
| skia_surface->output_bounds().y(), |
| skia_surface->output_bounds().width(), |
| skia_surface->output_bounds().height()); |
| |
| SkRect source_rect = SkRect::MakeWH(skia_surface->output_bounds().width(), |
| skia_surface->output_bounds().height()); |
| |
| #if defined(ENABLE_DEBUG_CONSOLE) |
| if (toggle_cache_highlights_) { |
| paint.setARGB(128, 255, 128, 128); |
| draw_state_->render_target->drawRect(dest_rect, paint); |
| } else // NOLINT(readability/braces) |
| #endif |
| { |
| draw_state_->render_target->drawImageRect(skia_surface->image(), |
| &source_rect, dest_rect, &paint); |
| } |
| |
| draw_state_->render_target->restore(); |
| } |
| |
| void SurfaceCacheDelegate::StartRecording(const math::RectF& local_bounds) { |
| TRACE_EVENT0("cobalt::renderer", "SurfaceCacheDelegate::StartRecording()"); |
| DCHECK(!recording_data_); |
| |
| // If the scale is less than one, render to the offscreen surface as if the |
| // scale is 1, since the scale may be animating and this way we do not need |
| // to rerender the subtree each frame of the animation. If the scale is |
| // larger than 1, we cache it as is and if it is animating, performance will |
| // suffer. |
| math::Vector2dF inv_scale(scale_.x() < 1.0f ? 1.0f / scale_.x() : 1.0f, |
| scale_.y() < 1.0f ? 1.0f / scale_.y() : 1.0f); |
| math::Matrix3F total_matrix = |
| SkiaMatrixToCobalt(draw_state_->render_target->getTotalMatrix()) * |
| math::ScaleMatrix(inv_scale); |
| |
| // Figure where we should render to the offscreen surface and then how to map |
| // that later to the onscreen surface when applying the cached surface. We |
| // ignore the viewport so that we cache all of partially on-screen surfaces. |
| common::OffscreenRenderCoordinateMapping coord_mapping = |
| common::GetOffscreenRenderCoordinateMapping(local_bounds, total_matrix, |
| base::optional<math::Rect>()); |
| // If the output has an area of 0 then there is nothing to cache. This should |
| // not be common. |
| if (coord_mapping.output_bounds.size().GetArea() == 0) { |
| recording_data_.emplace(*draw_state_, coord_mapping, |
| static_cast<SkSurface*>(NULL)); |
| return; |
| } |
| |
| #if !defined(NDEBUG) |
| // Ensure that the surface we ultimately render to is not larger than what |
| // we are reporting with GetRenderSize(). |
| math::Size render_size = GetRenderSize(local_bounds.size()); |
| DCHECK_LE(coord_mapping.output_bounds.width(), render_size.width()); |
| DCHECK_LE(coord_mapping.output_bounds.height(), render_size.height()); |
| #endif |
| |
| // Now create a SkSurface and set it up as the new canvas for our client to |
| // target. |
| SkSurface* surface = create_sk_surface_function_.Run( |
| math::Size(coord_mapping.output_bounds.width(), |
| coord_mapping.output_bounds.height())); |
| if (!surface) { |
| LOG(WARNING) << "Could not create a " << coord_mapping.output_bounds.width() |
| << "x" << coord_mapping.output_bounds.height() << "."; |
| NOTREACHED(); |
| } |
| |
| recording_data_.emplace(*draw_state_, coord_mapping, surface); |
| |
| SkCanvas* offscreen_canvas = surface->getCanvas(); |
| draw_state_->render_target = offscreen_canvas; |
| draw_state_->opacity = 1.0f; |
| |
| // Clear the draw area to RGBA(0, 0, 0, 0) for a fresh scratch surface before |
| // returning. |
| draw_state_->render_target->drawARGB(0, 0, 0, 0, SkXfermode::kClear_Mode); |
| |
| draw_state_->render_target->setMatrix( |
| CobaltMatrixToSkia(coord_mapping.sub_render_transform)); |
| } |
| |
| common::SurfaceCache::CachedSurface* SurfaceCacheDelegate::EndRecording() { |
| TRACE_EVENT0("cobalt::renderer", "SurfaceCacheDelegate::EndRecording()"); |
| DCHECK(recording_data_); |
| |
| *draw_state_ = recording_data_->original_draw_state; |
| |
| // Save the results as a CachedSurface and return them. |
| CachedSurface* cached_surface = |
| new CachedSurface(recording_data_->surface, |
| recording_data_->coord_mapping.output_post_scale, |
| recording_data_->coord_mapping.output_pre_translate, |
| recording_data_->coord_mapping.output_bounds); |
| |
| recording_data_ = base::nullopt; |
| |
| return cached_surface; |
| } |
| |
| void SurfaceCacheDelegate::ReleaseSurface( |
| common::SurfaceCache::CachedSurface* surface) { |
| TRACE_EVENT0("cobalt::renderer", "SurfaceCacheDelegate::ReleaseSurface()"); |
| delete surface; |
| } |
| |
| math::Size SurfaceCacheDelegate::GetRenderSize(const math::SizeF& local_size) { |
| // All surfaces scaled down would get rendered at their unscaled size, while |
| // surfaces scaled up will be rendered at their scaled size. This is so that |
| // we can keep in the cache items that have animating scale, as long as the |
| // scale is less than 1 (which is common). The cost is that we may use more |
| // memory than necessary for these down-scaled surfaces. |
| math::Vector2dF scale(std::max(1.0f, scale_.x()), std::max(1.0f, scale_.y())); |
| |
| // Add 2.0f to account for any rounding out that may occur. |
| math::Size size(static_cast<int>(local_size.width() * scale.x() + 2.0f), |
| static_cast<int>(local_size.height() * scale.y() + 2.0f)); |
| return size; |
| } |
| |
| math::Size SurfaceCacheDelegate::MaximumSurfaceSize() { |
| return max_surface_size_; |
| } |
| |
| } // namespace skia |
| } // namespace rasterizer |
| } // namespace renderer |
| } // namespace cobalt |