blob: 9622bb7c5a55841f88439c5fe46ef60e31064412 [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/render_tree_node_visitor.h"
#include <algorithm>
#include <cmath>
#include "base/debug/trace_event.h"
#include "base/optional.h"
#include "cobalt/base/polymorphic_downcast.h"
#include "cobalt/base/type_id.h"
#include "cobalt/math/matrix3_f.h"
#include "cobalt/math/transform_2d.h"
#include "cobalt/renderer/rasterizer/common/utils.h"
#include "cobalt/renderer/rasterizer/egl/draw_poly_color.h"
#include "cobalt/renderer/rasterizer/egl/draw_rect_color_texture.h"
#include "cobalt/renderer/rasterizer/egl/draw_rect_linear_gradient.h"
#include "cobalt/renderer/rasterizer/egl/draw_rect_shadow_blur.h"
#include "cobalt/renderer/rasterizer/egl/draw_rect_shadow_spread.h"
#include "cobalt/renderer/rasterizer/egl/draw_rect_texture.h"
#include "cobalt/renderer/rasterizer/skia/hardware_image.h"
#include "cobalt/renderer/rasterizer/skia/image.h"
namespace cobalt {
namespace renderer {
namespace rasterizer {
namespace egl {
namespace {
math::Rect RoundRectFToInt(const math::RectF& input) {
int left = static_cast<int>(input.x() + 0.5f);
int right = static_cast<int>(input.right() + 0.5f);
int top = static_cast<int>(input.y() + 0.5f);
int bottom = static_cast<int>(input.bottom() + 0.5f);
return math::Rect(left, top, right - left, bottom - top);
}
bool IsOnlyScaleAndTranslate(const math::Matrix3F& matrix) {
return matrix(2, 0) == 0 && matrix(2, 1) == 0 && matrix(2, 2) == 1 &&
matrix(0, 1) == 0 && matrix(1, 0) == 0;
}
math::Matrix3F GetTexcoordTransform(
const OffscreenTargetManager::TargetInfo& target) {
float scale_x = 1.0f / target.framebuffer->GetSize().width();
float scale_y = 1.0f / target.framebuffer->GetSize().height();
return math::Matrix3F::FromValues(
target.region.width() * scale_x, 0, target.region.x() * scale_x,
0, target.region.height() * scale_y, target.region.y() * scale_y,
0, 0, 1);
}
} // namespace
RenderTreeNodeVisitor::RenderTreeNodeVisitor(GraphicsState* graphics_state,
DrawObjectManager* draw_object_manager,
OffscreenTargetManager* offscreen_target_manager,
const FallbackRasterizeFunction* fallback_rasterize)
: graphics_state_(graphics_state),
draw_object_manager_(draw_object_manager),
offscreen_target_manager_(offscreen_target_manager),
fallback_rasterize_(fallback_rasterize) {
// Let the first draw object render in front of the clear depth.
draw_state_.depth = GraphicsState::NextClosestDepth(draw_state_.depth);
draw_state_.scissor.Intersect(graphics_state->GetViewport());
draw_state_.scissor.Intersect(graphics_state->GetScissor());
}
void RenderTreeNodeVisitor::Visit(
render_tree::CompositionNode* composition_node) {
const render_tree::CompositionNode::Builder& data = composition_node->data();
math::Matrix3F old_transform = draw_state_.transform;
draw_state_.transform = draw_state_.transform *
math::TranslateMatrix(data.offset().x(), data.offset().y());
const render_tree::CompositionNode::Children& children =
data.children();
for (render_tree::CompositionNode::Children::const_iterator iter =
children.begin(); iter != children.end(); ++iter) {
(*iter)->Accept(this);
}
draw_state_.transform = old_transform;
}
void RenderTreeNodeVisitor::Visit(
render_tree::MatrixTransform3DNode* transform_3d_node) {
// TODO: Ignore the 3D transform matrix for now.
transform_3d_node->data().source->Accept(this);
}
void RenderTreeNodeVisitor::Visit(
render_tree::MatrixTransformNode* transform_node) {
const render_tree::MatrixTransformNode::Builder& data =
transform_node->data();
math::Matrix3F old_transform = draw_state_.transform;
draw_state_.transform = draw_state_.transform *
data.transform;
data.source->Accept(this);
draw_state_.transform = old_transform;
}
void RenderTreeNodeVisitor::Visit(render_tree::FilterNode* filter_node) {
const render_tree::FilterNode::Builder& data = filter_node->data();
// If this is only a viewport filter w/o rounded edges, and the current
// transform matrix keeps the filter as an orthogonal rect, then collapse
// the node.
if (data.viewport_filter &&
!data.viewport_filter->has_rounded_corners() &&
!data.opacity_filter &&
!data.blur_filter &&
!data.map_to_mesh_filter) {
const math::Matrix3F& transform = draw_state_.transform;
if (IsOnlyScaleAndTranslate(transform)) {
// Transform local viewport to world viewport.
const math::RectF& filter_viewport = data.viewport_filter->viewport();
math::RectF transformed_viewport(
filter_viewport.x() * transform(0, 0) + transform(0, 2),
filter_viewport.y() * transform(1, 1) + transform(1, 2),
filter_viewport.width() * transform(0, 0),
filter_viewport.height() * transform(1, 1));
// Ensure transformed viewport data is sane (in case global transform
// flipped any axis).
if (transformed_viewport.width() < 0) {
transformed_viewport.set_x(transformed_viewport.right());
transformed_viewport.set_width(-transformed_viewport.width());
}
if (transformed_viewport.height() < 0) {
transformed_viewport.set_y(transformed_viewport.bottom());
transformed_viewport.set_height(-transformed_viewport.height());
}
// Combine the new viewport filter with existing viewport filter.
math::Rect old_scissor = draw_state_.scissor;
draw_state_.scissor.Intersect(RoundRectFToInt(transformed_viewport));
if (!draw_state_.scissor.IsEmpty()) {
data.source->Accept(this);
}
draw_state_.scissor = old_scissor;
return;
}
}
// Handle opacity-only filter.
if (data.opacity_filter &&
!data.viewport_filter &&
!data.blur_filter &&
!data.map_to_mesh_filter) {
int opacity = static_cast<int>(data.opacity_filter->opacity() * 255.0f);
if (opacity <= 0) {
// Totally transparent. Ignore the source.
return;
} else if (opacity >= 255) {
// Totally opaque. Render like normal.
data.source->Accept(this);
return;
} else if (common::utils::NodeCanRenderWithOpacity(data.source)) {
float old_opacity = draw_state_.opacity;
draw_state_.opacity *= data.opacity_filter->opacity();
data.source->Accept(this);
draw_state_.opacity = old_opacity;
return;
}
}
// Handle blur-only filter.
if (data.blur_filter &&
!data.viewport_filter &&
!data.opacity_filter &&
!data.map_to_mesh_filter) {
if (data.blur_filter->blur_sigma() == 0.0f) {
// Ignorable blur request. Render normally.
data.source->Accept(this);
return;
}
}
// No filter.
if (!data.opacity_filter &&
!data.viewport_filter &&
!data.blur_filter &&
!data.map_to_mesh_filter) {
data.source->Accept(this);
return;
}
// Use the fallback rasterizer to handle everything else.
FallbackRasterize(filter_node, DrawObjectManager::kOffscreenSkiaFilter);
}
void RenderTreeNodeVisitor::Visit(render_tree::ImageNode* image_node) {
const render_tree::ImageNode::Builder& data = image_node->data();
// The image node may contain nothing. For example, when it represents a video
// element before any frame is decoded.
if (!data.source) {
return;
}
if (!IsVisible(image_node->GetBounds())) {
return;
}
skia::Image* skia_image =
base::polymorphic_downcast<skia::Image*>(data.source.get());
bool clamp_texcoords = false;
bool is_opaque = skia_image->IsOpaque() && draw_state_.opacity == 1.0f;
// Ensure any required backend processing is done to create the necessary
// GPU resource.
skia_image->EnsureInitialized();
// Calculate matrix to transform texture coordinates according to the local
// transform.
math::Matrix3F texcoord_transform(math::Matrix3F::Identity());
if (IsOnlyScaleAndTranslate(data.local_transform)) {
texcoord_transform(0, 0) = data.local_transform(0, 0) != 0 ?
1.0f / data.local_transform(0, 0) : 0;
texcoord_transform(1, 1) = data.local_transform(1, 1) != 0 ?
1.0f / data.local_transform(1, 1) : 0;
texcoord_transform(0, 2) = -texcoord_transform(0, 0) *
data.local_transform(0, 2);
texcoord_transform(1, 2) = -texcoord_transform(1, 1) *
data.local_transform(1, 2);
if (texcoord_transform(0, 0) < 1.0f || texcoord_transform(1, 1) < 1.0f) {
// Edges may interpolate with texels outside the designated region.
// Use a fragment shader that clamps the texture coordinates to prevent
// that from happening.
clamp_texcoords = true;
}
} else {
texcoord_transform = data.local_transform.Inverse();
}
// Different shaders are used depending on whether the image has a single
// plane or multiple planes.
scoped_ptr<DrawObject> draw;
DrawObjectManager::OnscreenType onscreen_type;
if (skia_image->GetTypeId() == base::GetTypeId<skia::SinglePlaneImage>()) {
skia::HardwareFrontendImage* hardware_image =
base::polymorphic_downcast<skia::HardwareFrontendImage*>(skia_image);
if (clamp_texcoords || !is_opaque) {
onscreen_type = DrawObjectManager::kOnscreenRectColorTexture;
draw.reset(new DrawRectColorTexture(graphics_state_, draw_state_,
data.destination_rect,
render_tree::ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f),
hardware_image->GetTextureEGL(), texcoord_transform,
clamp_texcoords));
} else {
onscreen_type = DrawObjectManager::kOnscreenRectTexture;
draw.reset(new DrawRectTexture(graphics_state_, draw_state_,
data.destination_rect, hardware_image->GetTextureEGL(),
texcoord_transform));
}
} else if (skia_image->GetTypeId() ==
base::GetTypeId<skia::MultiPlaneImage>()) {
FallbackRasterize(image_node,
DrawObjectManager::kOffscreenSkiaMultiPlaneImage);
return;
} else {
NOTREACHED();
return;
}
if (is_opaque) {
AddOpaqueDraw(draw.Pass(), onscreen_type,
DrawObjectManager::kOffscreenNone);
} else {
AddTransparentDraw(draw.Pass(), onscreen_type,
DrawObjectManager::kOffscreenNone, image_node->GetBounds());
}
}
void RenderTreeNodeVisitor::Visit(
render_tree::PunchThroughVideoNode* video_node) {
if (!IsVisible(video_node->GetBounds())) {
return;
}
const render_tree::PunchThroughVideoNode::Builder& data = video_node->data();
math::RectF mapped_rect = draw_state_.transform.MapRect(data.rect);
data.set_bounds_cb.Run(
math::Rect(static_cast<int>(mapped_rect.x()),
static_cast<int>(mapped_rect.y()),
static_cast<int>(mapped_rect.width()),
static_cast<int>(mapped_rect.height())));
scoped_ptr<DrawObject> draw(new DrawPolyColor(graphics_state_,
draw_state_, data.rect, render_tree::ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f)));
AddOpaqueDraw(draw.Pass(), DrawObjectManager::kOnscreenPolyColor,
DrawObjectManager::kOffscreenNone);
}
void RenderTreeNodeVisitor::Visit(render_tree::RectNode* rect_node) {
if (!IsVisible(rect_node->GetBounds())) {
return;
}
const render_tree::RectNode::Builder& data = rect_node->data();
const scoped_ptr<render_tree::Brush>& brush = data.background_brush;
// Only solid color brushes are supported at this time.
const bool brush_supported = !brush ||
brush->GetTypeId() == base::GetTypeId<render_tree::SolidColorBrush>() ||
brush->GetTypeId() == base::GetTypeId<render_tree::LinearGradientBrush>();
// Borders are not supported natively by this rasterizer at this time. The
// difficulty lies in getting anti-aliased borders and minimizing state
// switches (due to anti-aliased borders requiring transparency). However,
// by using the fallback rasterizer, both can be accomplished -- sort to
// minimize state switches while rendering anti-aliased borders to the
// offscreen target, then use a single shader to render those.
const bool border_supported = !data.border;
if (data.rounded_corners) {
FallbackRasterize(rect_node, DrawObjectManager::kOffscreenSkiaRectRounded);
} else if (!brush_supported) {
FallbackRasterize(rect_node, DrawObjectManager::kOffscreenSkiaRectBrush);
} else if (!border_supported) {
FallbackRasterize(rect_node, DrawObjectManager::kOffscreenSkiaRectBorder);
} else {
DCHECK(!data.border);
const math::RectF& content_rect(data.rect);
// Handle drawing the content.
if (brush) {
base::TypeId brush_type = brush->GetTypeId();
if (brush_type == base::GetTypeId<render_tree::SolidColorBrush>()) {
const render_tree::SolidColorBrush* solid_brush =
base::polymorphic_downcast<const render_tree::SolidColorBrush*>
(brush.get());
const render_tree::ColorRGBA& brush_color(solid_brush->color());
render_tree::ColorRGBA content_color(
brush_color.r() * brush_color.a(),
brush_color.g() * brush_color.a(),
brush_color.b() * brush_color.a(),
brush_color.a());
scoped_ptr<DrawObject> draw(new DrawPolyColor(graphics_state_,
draw_state_, content_rect, content_color));
if (draw_state_.opacity * content_color.a() == 1.0f) {
AddOpaqueDraw(draw.Pass(), DrawObjectManager::kOnscreenPolyColor,
DrawObjectManager::kOffscreenNone);
} else {
AddTransparentDraw(draw.Pass(), DrawObjectManager::kOnscreenPolyColor,
DrawObjectManager::kOffscreenNone, rect_node->GetBounds());
}
} else {
const render_tree::LinearGradientBrush* linear_brush =
base::polymorphic_downcast<const render_tree::LinearGradientBrush*>
(brush.get());
scoped_ptr<DrawObject> draw(new DrawRectLinearGradient(graphics_state_,
draw_state_, content_rect, *linear_brush));
// The draw may use transparent colors or a depth stencil (but only
// the inclusive one, so only one depth value is needed), so make it
// a transparent draw.
AddTransparentDraw(draw.Pass(), DrawObjectManager::kOnscreenPolyColor,
DrawObjectManager::kOffscreenNone, rect_node->GetBounds());
}
}
}
}
void RenderTreeNodeVisitor::Visit(render_tree::RectShadowNode* shadow_node) {
math::RectF node_bounds(shadow_node->GetBounds());
if (!IsVisible(node_bounds)) {
return;
}
const render_tree::RectShadowNode::Builder& data = shadow_node->data();
if (data.rounded_corners) {
FallbackRasterize(shadow_node, DrawObjectManager::kOffscreenSkiaShadow);
return;
}
scoped_ptr<DrawObject> draw;
render_tree::ColorRGBA shadow_color(
data.shadow.color.r() * data.shadow.color.a(),
data.shadow.color.g() * data.shadow.color.a(),
data.shadow.color.b() * data.shadow.color.a(),
data.shadow.color.a());
DrawObjectManager::OnscreenType onscreen_type =
DrawObjectManager::kOnscreenRectShadow;
math::RectF spread_rect(data.rect);
spread_rect.Offset(data.shadow.offset);
if (data.inset) {
// data.rect is outermost.
// spread_rect is in the middle.
// blur_rect is innermost.
spread_rect.Inset(data.spread, data.spread);
spread_rect.Intersect(data.rect);
if (spread_rect.IsEmpty()) {
// Spread covers the whole data.rect.
spread_rect.set_origin(data.rect.CenterPoint());
spread_rect.set_size(math::SizeF());
}
if (!spread_rect.IsEmpty() && data.shadow.blur_sigma > 0.0f) {
math::RectF blur_rect(spread_rect);
math::Vector2dF blur_extent(data.shadow.BlurExtent());
blur_rect.Inset(blur_extent.x(), blur_extent.y());
blur_rect.Intersect(spread_rect);
if (blur_rect.IsEmpty()) {
// Blur covers all of spread.
blur_rect.set_origin(spread_rect.CenterPoint());
blur_rect.set_size(math::SizeF());
}
draw.reset(new DrawRectShadowBlur(graphics_state_, draw_state_,
blur_rect, data.rect, spread_rect, shadow_color, math::RectF(),
data.shadow.blur_sigma, data.inset));
onscreen_type = DrawObjectManager::kOnscreenRectShadowBlur;
} else {
draw.reset(new DrawRectShadowSpread(graphics_state_, draw_state_,
spread_rect, data.rect, shadow_color, data.rect, math::RectF()));
}
} else {
// blur_rect is outermost.
// spread_rect is in the middle (barring negative |spread| values).
// data.rect is innermost (though it may not overlap due to offset).
spread_rect.Outset(data.spread, data.spread);
if (spread_rect.IsEmpty()) {
// Negative spread shenanigans! Nothing to draw.
return;
}
math::RectF blur_rect(spread_rect);
if (data.shadow.blur_sigma > 0.0f) {
math::Vector2dF blur_extent(data.shadow.BlurExtent());
blur_rect.Outset(blur_extent.x(), blur_extent.y());
draw.reset(new DrawRectShadowBlur(graphics_state_, draw_state_,
data.rect, blur_rect, spread_rect, shadow_color, data.rect,
data.shadow.blur_sigma, data.inset));
onscreen_type = DrawObjectManager::kOnscreenRectShadowBlur;
} else {
draw.reset(new DrawRectShadowSpread(graphics_state_, draw_state_,
data.rect, spread_rect, shadow_color, spread_rect, data.rect));
}
node_bounds.Union(blur_rect);
}
// Include or exclude scissor will touch these pixels.
node_bounds.Union(data.rect);
// Since the depth buffer is polluted to create a stencil for pixels to be
// modified by the shadow, this draw must occur during the transparency
// pass. During this pass, all subsequent draws are guaranteed to be closer
// (i.e. pass the depth test) than pixels modified by previous transparency
// draws.
AddTransparentDraw(draw.Pass(), onscreen_type,
DrawObjectManager::kOffscreenNone, node_bounds);
// Since the box shadow draw objects use the depth stencil object, two depth
// values were used. So skip an additional depth value.
draw_state_.depth = GraphicsState::NextClosestDepth(draw_state_.depth);
}
void RenderTreeNodeVisitor::Visit(render_tree::TextNode* text_node) {
if (!IsVisible(text_node->GetBounds())) {
return;
}
FallbackRasterize(text_node, DrawObjectManager::kOffscreenSkiaText);
}
void RenderTreeNodeVisitor::FallbackRasterize(
scoped_refptr<render_tree::Node> node,
DrawObjectManager::OffscreenType offscreen_type) {
DCHECK_NE(offscreen_type, DrawObjectManager::kOffscreenNone);
math::RectF node_bounds(node->GetBounds());
math::RectF mapped_bounds(draw_state_.transform.MapRect(node_bounds));
if (mapped_bounds.IsEmpty()) {
return;
}
// Use the fallback rasterizer to render the tree. In order to preserve
// sharpness, let the fallback rasterizer handle the current transform.
math::Matrix3F old_transform = draw_state_.transform;
draw_state_.transform = math::Matrix3F::Identity();
// Request a slightly larger render target than the calculated bounds. The
// fallback rasterizer may use an extra pixel along the edge of anti-aliased
// objects.
const float kBorderWidth = 1.0f;
// The render target cache keys off the render_tree Node and target size.
// Since the size is rounded, it is possible to be a fraction of a pixel
// off. However, this in turn allows for a cache hit for "close-enough"
// renderings. To minimize offset errors, use the nearest full pixel offset.
const float kFractionPad = 1.0f;
float offset_x = std::floor(mapped_bounds.x() + 0.5f);
float offset_y = std::floor(mapped_bounds.y() + 0.5f);
math::PointF content_offset(
// Shift contents towards the origin of the render target.
kBorderWidth + kFractionPad - offset_x,
kBorderWidth + kFractionPad - offset_y);
math::SizeF content_size(
std::ceil(mapped_bounds.width() + 2.0f * kBorderWidth + kFractionPad),
std::ceil(mapped_bounds.height() + 2.0f * kBorderWidth + kFractionPad));
OffscreenTargetManager::TargetInfo target_info;
bool is_cached = offscreen_target_manager_->GetCachedOffscreenTarget(node,
content_size, &target_info);
if (!is_cached) {
offscreen_target_manager_->AllocateOffscreenTarget(node,
content_size, &target_info);
}
// If the render target is the scratch surface, then just render what fits
// onscreen for better performance.
if (target_info.is_scratch_surface) {
mapped_bounds.Outset(kBorderWidth, kBorderWidth);
mapped_bounds.Intersect(draw_state_.scissor);
if (mapped_bounds.IsEmpty()) {
return;
}
float left = std::floor(mapped_bounds.x());
float right = std::ceil(mapped_bounds.right());
float top = std::floor(mapped_bounds.y());
float bottom = std::ceil(mapped_bounds.bottom());
content_offset.SetPoint(-left, -top);
content_size.SetSize(right - left, bottom - top);
}
// The returned target may be larger than actually needed. Clamp to just the
// needed size.
DCHECK_LE(content_size.width(), target_info.region.width());
DCHECK_LE(content_size.height(), target_info.region.height());
target_info.region.set_size(content_size);
math::RectF draw_rect(-content_offset.x(), -content_offset.y(),
content_size.width(), content_size.height());
// Setup draw callbacks as needed.
base::Closure draw_offscreen;
base::Closure draw_onscreen;
if (!is_cached) {
// Pre-translate the contents so it starts near the origin.
math::Matrix3F content_transform(old_transform);
content_transform(0, 2) += content_offset.x();
content_transform(1, 2) += content_offset.y();
if (target_info.is_scratch_surface) {
draw_onscreen = base::Bind(*fallback_rasterize_,
scoped_refptr<render_tree::Node>(node), content_transform,
target_info);
} else {
draw_offscreen = base::Bind(*fallback_rasterize_,
scoped_refptr<render_tree::Node>(node), content_transform,
target_info);
}
}
// Create the appropriate draw object to call the draw callback, then render
// its results onscreen.
backend::TextureEGL* texture = target_info.framebuffer->GetColorTexture();
math::Matrix3F texcoord_transform = GetTexcoordTransform(target_info);
if (draw_state_.opacity == 1.0f) {
scoped_ptr<DrawObject> draw(new DrawRectTexture(graphics_state_,
draw_state_, draw_rect, texture, texcoord_transform,
draw_offscreen, draw_onscreen));
AddTransparentDraw(draw.Pass(), DrawObjectManager::kOnscreenRectTexture,
offscreen_type, draw_rect);
} else {
scoped_ptr<DrawObject> draw(new DrawRectColorTexture(graphics_state_,
draw_state_, draw_rect, render_tree::ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f),
texture, texcoord_transform, draw_offscreen, draw_onscreen));
AddTransparentDraw(draw.Pass(),
DrawObjectManager::kOnscreenRectColorTexture, offscreen_type,
draw_rect);
}
draw_state_.transform = old_transform;
}
bool RenderTreeNodeVisitor::IsVisible(const math::RectF& bounds) {
math::RectF intersection = IntersectRects(
draw_state_.transform.MapRect(bounds), draw_state_.scissor);
return !intersection.IsEmpty();
}
void RenderTreeNodeVisitor::AddOpaqueDraw(scoped_ptr<DrawObject> object,
DrawObjectManager::OnscreenType onscreen_type,
DrawObjectManager::OffscreenType offscreen_type) {
draw_object_manager_->AddOpaqueDraw(object.Pass(), onscreen_type,
offscreen_type);
draw_state_.depth = GraphicsState::NextClosestDepth(draw_state_.depth);
}
void RenderTreeNodeVisitor::AddTransparentDraw(scoped_ptr<DrawObject> object,
DrawObjectManager::OnscreenType onscreen_type,
DrawObjectManager::OffscreenType offscreen_type,
const math::RectF& local_bounds) {
draw_object_manager_->AddTransparentDraw(object.Pass(), onscreen_type,
offscreen_type, draw_state_.transform.MapRect(local_bounds));
draw_state_.depth = GraphicsState::NextClosestDepth(draw_state_.depth);
}
} // namespace egl
} // namespace rasterizer
} // namespace renderer
} // namespace cobalt