blob: 2a31b62664bc83854c48e5cc19c10fa254ae20b1 [file] [log] [blame]
// Copyright 2016 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
//
// 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/blitter/cached_software_rasterizer.h"
#include <utility>
#include "cobalt/math/vector2d_f.h"
#include "cobalt/render_tree/text_node.h"
#include "cobalt/renderer/rasterizer/blitter/skia_blitter_conversions.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImageInfo.h"
#if SB_API_VERSION < 12 && SB_HAS(BLITTER)
namespace cobalt {
namespace renderer {
namespace rasterizer {
namespace blitter {
size_t CachedSoftwareRasterizer::Surface::GetEstimatedMemoryUsage() const {
// We assume 4-bytes-per-pixel.
return coord_mapping.output_bounds.size().GetArea() * 4;
}
CachedSoftwareRasterizer::CachedSoftwareRasterizer(
SbBlitterDevice device, SbBlitterContext context, int cache_capacity,
bool purge_skia_font_caches_on_destruction)
: cache_capacity_(cache_capacity),
device_(device),
context_(context),
software_rasterizer_(purge_skia_font_caches_on_destruction),
cache_memory_usage_(
"Memory.CachedSoftwareRasterizer.CacheUsage", 0,
"Total memory occupied by cached software-rasterized surfaces."),
cache_frame_usage_(
"Memory.CachedSoftwareRasterizer.FrameCacheUsage", 0,
"Total memory occupied by cache software-rasterizer surfaces that "
"were referenced this frame.") {}
CachedSoftwareRasterizer::~CachedSoftwareRasterizer() {
// Clean up any leftover surfaces.
for (CacheMap::iterator iter = surface_map_.begin();
iter != surface_map_.end(); ++iter) {
DCHECK(iter->second.cached);
SbBlitterDestroySurface(iter->second.surface);
}
surface_map_.clear();
}
void CachedSoftwareRasterizer::OnStartNewFrame() {
for (CacheMap::iterator iter = surface_map_.begin();
iter != surface_map_.end();) {
CacheMap::iterator current = iter;
++iter;
// If the surface was referenced mark it as being unreferenced for the next
// frame.
if (current->second.referenced) {
#if defined(ENABLE_DEBUGGER)
current->second.created = false;
#endif // defined(ENABLE_DEBUGGER)
current->second.referenced = false;
}
}
// Reset our current frame cache usage to 0 since this is the start of a new
// frame.
cache_frame_usage_ = 0;
}
void CachedSoftwareRasterizer::PurgeUntilSpaceAvailable(int space_needed) {
while (space_needed + cache_memory_usage_.value() > cache_capacity_) {
CacheMap::iterator next_item = surface_map_.begin();
// We shouldn't call this function if it means we would have to purge
// elements that were referenced this frame. This is to avoid thrashing
// the cache, we would prefer to cache as much as we can in a frame, and
// then just not cache whatever we can't without cycling what's in the cache
// already.
DCHECK(!next_item->second.referenced);
cache_memory_usage_ -= next_item->second.GetEstimatedMemoryUsage();
SbBlitterDestroySurface(next_item->second.surface);
surface_map_.erase(next_item);
}
}
CachedSoftwareRasterizer::Surface CachedSoftwareRasterizer::GetSurface(
render_tree::Node* node, const Transform& transform) {
CacheMap::iterator found = surface_map_.find(node);
if (found != surface_map_.end()) {
#if SB_HAS(BILINEAR_FILTERING_SUPPORT)
if (found->second.scale.x() >= transform.scale().x() &&
found->second.scale.y() >= transform.scale().y()) {
#else // SB_HAS(BILINEAR_FILTERING_SUPPORT)
if (found->second.scale.x() == transform.scale().x() &&
found->second.scale.y() == transform.scale().y()) {
#endif
std::pair<render_tree::Node*, Surface> to_insert =
std::make_pair(node, found->second);
// Move this surface's position in the queue to the front, since it was
// referenced.
to_insert.second.referenced = true;
surface_map_.erase(found);
surface_map_.insert(to_insert);
cache_frame_usage_ += to_insert.second.GetEstimatedMemoryUsage();
return to_insert.second;
}
}
Surface software_surface;
software_surface.referenced = true;
#if defined(ENABLE_DEBUGGER)
software_surface.created = true;
#endif // defined(ENABLE_DEBUGGER)
software_surface.node = node;
software_surface.surface = kSbBlitterInvalidSurface;
software_surface.cached = false;
// We ensure that scale is at least 1 except for TextNodes, since the Blitter
// scaling is not as sophisticated as Skia's. Since it is common to have
// animating scale between 0 and 1 (e.g. an item animating from 0 to 1 to
// "grow" into the scene), this ensures that rendered items will not look
// pixellated as they grow (e.g. if they were cached at scale 0.2, they would
// be stretched x5 if the scale were to animate to 1.0).
if (transform.scale().x() == 0.0f || transform.scale().y() == 0.0f) {
// There won't be anything to render if the transform squishes one dimension
// to 0.
return software_surface;
}
DCHECK_LT(0, transform.scale().x());
DCHECK_LT(0, transform.scale().y());
Transform scaled_transform(transform);
// If bilinear filtering is not supported, do not rely on the rasterizer to
// scale down for us, it results in too many artifacts.
#if SB_HAS(BILINEAR_FILTERING_SUPPORT)
math::Vector2dF apply_scale(1.0f, 1.0f);
if (node->GetTypeId() != base::GetTypeId<render_tree::TextNode>()) {
if (transform.scale().x() < 1.0f) {
apply_scale.set_x(1.0f / transform.scale().x());
}
if (transform.scale().y() < 1.0f) {
apply_scale.set_y(1.0f / transform.scale().y());
}
}
scaled_transform.ApplyScale(apply_scale);
#endif // SB_HAS(BILINEAR_FILTERING_SUPPORT)
software_surface.scale = scaled_transform.scale();
common::OffscreenRenderCoordinateMapping coord_mapping =
common::GetOffscreenRenderCoordinateMapping(node->GetBounds(),
scaled_transform.ToMatrix(),
base::Optional<math::Rect>());
software_surface.coord_mapping = coord_mapping;
if (coord_mapping.output_bounds.IsEmpty()) {
// There's nothing to render if the bounds are 0.
return software_surface;
}
DCHECK_GE(0.001f, std::abs(1.0f - scaled_transform.scale().x() *
coord_mapping.output_post_scale.x()));
DCHECK_GE(0.001f, std::abs(1.0f - scaled_transform.scale().y() *
coord_mapping.output_post_scale.y()));
SkImageInfo output_image_info = SkImageInfo::MakeN32(
coord_mapping.output_bounds.width(), coord_mapping.output_bounds.height(),
kPremul_SkAlphaType);
// Allocate the pixels for the output image.
SbBlitterPixelDataFormat blitter_pixel_data_format =
SkiaToBlitterPixelFormat(output_image_info.colorType());
DCHECK(SbBlitterIsPixelFormatSupportedByPixelData(device_,
blitter_pixel_data_format));
SbBlitterPixelData pixel_data = SbBlitterCreatePixelData(
device_, coord_mapping.output_bounds.width(),
coord_mapping.output_bounds.height(), blitter_pixel_data_format);
if (!SbBlitterIsPixelDataValid(pixel_data)) {
// We failed to allocate the pixel data, just return with a null surface
// in this case.
LOG(ERROR) << "Error allocating pixel data for an offscreen software "
"rasterization.";
return software_surface;
}
SkBitmap bitmap;
bitmap.installPixels(output_image_info,
SbBlitterGetPixelDataPointer(pixel_data),
SbBlitterGetPixelDataPitchInBytes(pixel_data));
// Setup our Skia canvas that we will be using as the target for all CPU Skia
// output.
SkCanvas canvas(bitmap);
canvas.clear(SkColorSetARGB(0, 0, 0, 0));
Transform sub_render_transform(coord_mapping.sub_render_transform);
// Now setup our canvas so that the render tree will be rendered to the top
// left corner instead of at node->GetBounds().origin().
canvas.translate(sub_render_transform.translate().x(),
sub_render_transform.translate().y());
// And finally set the scale on our target canvas to match that of the current
// |transform|.
canvas.scale(sub_render_transform.scale().x(),
sub_render_transform.scale().y());
// Use the Skia software rasterizer to render our subtree.
software_rasterizer_.Submit(node, &canvas);
// Create a surface out of the now populated pixel data.
software_surface.surface =
SbBlitterCreateSurfaceFromPixelData(device_, pixel_data);
if (software_surface.GetEstimatedMemoryUsage() + cache_frame_usage_.value() <=
cache_capacity_) {
software_surface.cached = true;
cache_frame_usage_ += software_surface.GetEstimatedMemoryUsage();
if (found != surface_map_.end()) {
// This surface may have already been in the cache if it was in there
// with a different scale. In that case, replace the old one.
cache_memory_usage_ -= found->second.GetEstimatedMemoryUsage();
SbBlitterDestroySurface(found->second.surface);
surface_map_.erase(found);
}
PurgeUntilSpaceAvailable(software_surface.GetEstimatedMemoryUsage());
std::pair<CacheMap::iterator, bool> inserted =
surface_map_.insert(std::make_pair(node, software_surface));
cache_memory_usage_ += software_surface.GetEstimatedMemoryUsage();
}
return software_surface;
}
} // namespace blitter
} // namespace rasterizer
} // namespace renderer
} // namespace cobalt
#endif // SB_API_VERSION < 12 && SB_HAS(BLITTER)