synchronous_compositor_output_surface.cc revision 438599f994082010ffd07abcbecb7a97956451e2
1aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko// Copyright 2013 The Chromium Authors. All rights reserved. 2aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko// Use of this source code is governed by a BSD-style license that can be 3aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko// found in the LICENSE file. 4aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko 5aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko#include "content/browser/android/in_process/synchronous_compositor_output_surface.h" 6aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko 7aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko#include "base/auto_reset.h" 8aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko#include "base/logging.h" 9aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko#include "cc/output/begin_frame_args.h" 10aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko#include "cc/output/compositor_frame.h" 11e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko#include "cc/output/context_provider.h" 12aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko#include "cc/output/managed_memory_policy.h" 13aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko#include "cc/output/output_surface_client.h" 14aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko#include "cc/output/software_output_device.h" 15aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko#include "content/browser/android/in_process/synchronous_compositor_impl.h" 16e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko#include "content/common/gpu/client/webgraphicscontext3d_command_buffer_impl.h" 17aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko#include "content/public/browser/browser_thread.h" 186ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko#include "gpu/command_buffer/client/gl_in_process_context.h" 196ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko#include "third_party/skia/include/core/SkCanvas.h" 206ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko#include "third_party/skia/include/core/SkDevice.h" 216ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko#include "ui/gfx/rect_conversions.h" 226ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko#include "ui/gfx/skia_util.h" 236ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko#include "ui/gfx/transform.h" 246ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko#include "ui/gl/gl_surface.h" 256ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko#include "webkit/common/gpu/webgraphicscontext3d_in_process_command_buffer_impl.h" 266ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko 276ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko 286ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenkonamespace content { 296ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko 306ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenkonamespace { 316ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko 326ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenkoscoped_ptr<WebKit::WebGraphicsContext3D> CreateWebGraphicsContext3D( 33aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko scoped_refptr<gfx::GLSurface> surface) { 34e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko using webkit::gpu::WebGraphicsContext3DInProcessCommandBufferImpl; 35e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko if (!gfx::GLSurface::InitializeOneOff()) 36e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko return scoped_ptr<WebKit::WebGraphicsContext3D>(); 37e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko 38aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko const char* allowed_extensions = "*"; 39aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko const gfx::GpuPreference gpu_preference = gfx::PreferDiscreteGpu; 40e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko 41e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko WebKit::WebGraphicsContext3D::Attributes attributes; 42e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko attributes.antialias = false; 43e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko attributes.shareResources = true; 44e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko attributes.noAutomaticFlushes = true; 45aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko 46ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian gpu::GLInProcessContextAttribs in_process_attribs; 47ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian WebGraphicsContext3DInProcessCommandBufferImpl::ConvertAttributes( 48ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian attributes, &in_process_attribs); 490089bc4ddee6bb309ad25f4c7ad4b7ffe5df4512Fariborz Jahanian scoped_ptr<gpu::GLInProcessContext> context( 500089bc4ddee6bb309ad25f4c7ad4b7ffe5df4512Fariborz Jahanian gpu::GLInProcessContext::CreateWithSurface(surface, 51ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian attributes.shareResources, 52ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian allowed_extensions, 53ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian in_process_attribs, 54ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian gpu_preference)); 55ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian 56ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian if (!context.get()) 57ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian return scoped_ptr<WebKit::WebGraphicsContext3D>(); 58ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian 59ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian return scoped_ptr<WebKit::WebGraphicsContext3D>( 60ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian WebGraphicsContext3DInProcessCommandBufferImpl::WrapContext( 61ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian context.Pass(), attributes)); 62ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian} 63ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian 64ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanianvoid DidActivatePendingTree(int routing_id) { 65ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian SynchronousCompositorOutputSurfaceDelegate* delegate = 66ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian SynchronousCompositorImpl::FromRoutingID(routing_id); 67ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian if (delegate) 68ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian delegate->DidActivatePendingTree(); 69ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian} 70ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian 71f4030ae4638d1831a2a031f1e33b86de8c5fef28Fariborz Jahanian} // namespace 72f4030ae4638d1831a2a031f1e33b86de8c5fef28Fariborz Jahanian 73f4030ae4638d1831a2a031f1e33b86de8c5fef28Fariborz Jahanianclass SynchronousCompositorOutputSurface::SoftwareDevice 74f4030ae4638d1831a2a031f1e33b86de8c5fef28Fariborz Jahanian : public cc::SoftwareOutputDevice { 75f4030ae4638d1831a2a031f1e33b86de8c5fef28Fariborz Jahanian public: 760089bc4ddee6bb309ad25f4c7ad4b7ffe5df4512Fariborz Jahanian SoftwareDevice(SynchronousCompositorOutputSurface* surface) 770089bc4ddee6bb309ad25f4c7ad4b7ffe5df4512Fariborz Jahanian : surface_(surface), 78b960232518a1cd79c5f64ab5ef54c88e34660191Craig Topper null_device_(SkBitmap::kARGB_8888_Config, 1, 1), 79ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian null_canvas_(&null_device_) { 80ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian } 81ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian virtual void Resize(gfx::Size size) OVERRIDE { 82ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian // Intentional no-op: canvas size is controlled by the embedder. 83ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian } 84ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian virtual SkCanvas* BeginPaint(gfx::Rect damage_rect) OVERRIDE { 85ad91e5431f2f2d0a32ccdff8c55e7fa921af42bdFariborz Jahanian if (!surface_->current_sw_canvas_) { 860089bc4ddee6bb309ad25f4c7ad4b7ffe5df4512Fariborz Jahanian NOTREACHED() << "BeginPaint with no canvas set"; 870089bc4ddee6bb309ad25f4c7ad4b7ffe5df4512Fariborz Jahanian return &null_canvas_; 880089bc4ddee6bb309ad25f4c7ad4b7ffe5df4512Fariborz Jahanian } 896ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko LOG_IF(WARNING, surface_->did_swap_buffer_) 90116bb09882bc1c9281cd84dd07496201feb18d18Eli Friedman << "Mutliple calls to BeginPaint per frame"; 91e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko return surface_->current_sw_canvas_; 92116bb09882bc1c9281cd84dd07496201feb18d18Eli Friedman } 93aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko virtual void EndPaint(cc::SoftwareFrameData* frame_data) OVERRIDE { 94e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko } 95e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko virtual void CopyToBitmap(gfx::Rect rect, SkBitmap* output) OVERRIDE { 96e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko NOTIMPLEMENTED(); 97e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko } 98aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko 99e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko private: 100aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko SynchronousCompositorOutputSurface* surface_; 101e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko SkDevice null_device_; 102aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko SkCanvas null_canvas_; 103aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko 1046ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko DISALLOW_COPY_AND_ASSIGN(SoftwareDevice); 1056ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko}; 1066ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko 1076ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri GribenkoSynchronousCompositorOutputSurface::SynchronousCompositorOutputSurface( 1086ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko int routing_id) 1096ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko : cc::OutputSurface( 1106ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko scoped_ptr<cc::SoftwareOutputDevice>(new SoftwareDevice(this))), 1116ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko routing_id_(routing_id), 1126ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko needs_begin_frame_(false), 1136ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko invoking_composite_(false), 1146ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko did_swap_buffer_(false), 1156ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko current_sw_canvas_(NULL) { 1166ebf09130479bc7605aa09a3e6c4dc2ba3513495Dmitri Gribenko capabilities_.deferred_gl_initialization = true; 117e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko capabilities_.draw_and_swap_full_viewport_every_frame = true; 118e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko capabilities_.adjust_deadline_for_parent = false; 119e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko // Cannot call out to GetDelegate() here as the output surface is not 120e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko // constructed on the correct thread. 121e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko} 12262290ae569016345b79d4e11dd93abc300e5a681Dmitri Gribenko 12362290ae569016345b79d4e11dd93abc300e5a681Dmitri GribenkoSynchronousCompositorOutputSurface::~SynchronousCompositorOutputSurface() { 124e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko DCHECK(CalledOnValidThread()); 125e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko SynchronousCompositorOutputSurfaceDelegate* delegate = GetDelegate(); 126e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko if (delegate) 127e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko delegate->DidDestroySynchronousOutputSurface(this); 128e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko} 129e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko 130e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenkobool SynchronousCompositorOutputSurface::ForcedDrawToSoftwareDevice() const { 131aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko // |current_sw_canvas_| indicates we're in a DemandDrawSw call. In addition 132aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko // |invoking_composite_| == false indicates an attempt to draw outside of 133e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko // the synchronous compositor's control: force it into SW path and hence to 134e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko // the null canvas (and will log a warning there). 135e4330a302ac20b41b9800267ebd4b5b01f8553f8Dmitri Gribenko return current_sw_canvas_ != NULL || !invoking_composite_; 136aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko} 137aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko 138aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenkobool SynchronousCompositorOutputSurface::BindToClient( 139aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko cc::OutputSurfaceClient* surface_client) { 140aa58081902ad31927df02e8537d972eabe29d6dfDmitri Gribenko DCHECK(CalledOnValidThread()); 141 if (!cc::OutputSurface::BindToClient(surface_client)) 142 return false; 143 surface_client->SetTreeActivationCallback( 144 base::Bind(&DidActivatePendingTree, routing_id_)); 145 SynchronousCompositorOutputSurfaceDelegate* delegate = GetDelegate(); 146 if (delegate) 147 delegate->DidBindOutputSurface(this); 148 149 const int bytes_limit = 64 * 1024 * 1024; 150 const int num_resources_limit = 100; 151 surface_client->SetMemoryPolicy( 152 cc::ManagedMemoryPolicy(bytes_limit, 153 cc::ManagedMemoryPolicy::CUTOFF_ALLOW_EVERYTHING, 154 0, 155 cc::ManagedMemoryPolicy::CUTOFF_ALLOW_NOTHING, 156 num_resources_limit)); 157 158 return true; 159} 160 161void SynchronousCompositorOutputSurface::Reshape( 162 gfx::Size size, float scale_factor) { 163 // Intentional no-op: surface size is controlled by the embedder. 164} 165 166void SynchronousCompositorOutputSurface::SetNeedsBeginFrame( 167 bool enable) { 168 DCHECK(CalledOnValidThread()); 169 cc::OutputSurface::SetNeedsBeginFrame(enable); 170 needs_begin_frame_ = enable; 171 SynchronousCompositorOutputSurfaceDelegate* delegate = GetDelegate(); 172 if (delegate) 173 delegate->SetContinuousInvalidate(needs_begin_frame_); 174} 175 176void SynchronousCompositorOutputSurface::SwapBuffers( 177 cc::CompositorFrame* frame) { 178 if (!ForcedDrawToSoftwareDevice()) { 179 DCHECK(context3d()); 180 context3d()->shallowFlushCHROMIUM(); 181 } 182 SynchronousCompositorOutputSurfaceDelegate* delegate = GetDelegate(); 183 if (delegate) 184 delegate->UpdateFrameMetaData(frame->metadata); 185 186 did_swap_buffer_ = true; 187 DidSwapBuffers(); 188} 189 190namespace { 191void AdjustTransform(gfx::Transform* transform, 192 gfx::Rect viewport, 193 gfx::Rect clip) { 194 // CC's draw origin starts at the viewport. 195 transform->matrix().postTranslate(-viewport.x(), -viewport.y(), 0); 196} 197} // namespace 198 199bool SynchronousCompositorOutputSurface::InitializeHwDraw( 200 scoped_refptr<gfx::GLSurface> surface, 201 scoped_refptr<cc::ContextProvider> offscreen_context) { 202 DCHECK(CalledOnValidThread()); 203 DCHECK(HasClient()); 204 DCHECK(!context3d_); 205 DCHECK(surface); 206 207 return InitializeAndSetContext3D( 208 CreateWebGraphicsContext3D(surface).Pass(), offscreen_context); 209} 210 211void SynchronousCompositorOutputSurface::ReleaseHwDraw() { 212 cc::OutputSurface::ReleaseGL(); 213} 214 215bool SynchronousCompositorOutputSurface::DemandDrawHw( 216 gfx::Size surface_size, 217 const gfx::Transform& transform, 218 gfx::Rect viewport, 219 gfx::Rect clip, 220 bool stencil_enabled) { 221 DCHECK(CalledOnValidThread()); 222 DCHECK(HasClient()); 223 DCHECK(context3d()); 224 225 surface_size_ = surface_size; 226 SetExternalStencilTest(stencil_enabled); 227 InvokeComposite(transform, viewport, clip, true); 228 229 return did_swap_buffer_; 230} 231 232bool SynchronousCompositorOutputSurface::DemandDrawSw(SkCanvas* canvas) { 233 DCHECK(CalledOnValidThread()); 234 DCHECK(canvas); 235 DCHECK(!current_sw_canvas_); 236 base::AutoReset<SkCanvas*> canvas_resetter(¤t_sw_canvas_, canvas); 237 238 SkIRect canvas_clip; 239 canvas->getClipDeviceBounds(&canvas_clip); 240 gfx::Rect clip = gfx::SkIRectToRect(canvas_clip); 241 242 gfx::Transform transform(gfx::Transform::kSkipInitialization); 243 transform.matrix() = canvas->getTotalMatrix(); // Converts 3x3 matrix to 4x4. 244 245 surface_size_ = gfx::Size(canvas->getDeviceSize().width(), 246 canvas->getDeviceSize().height()); 247 SetExternalStencilTest(false); 248 249 InvokeComposite(transform, clip, clip, false); 250 251 return did_swap_buffer_; 252} 253 254void SynchronousCompositorOutputSurface::InvokeComposite( 255 const gfx::Transform& transform, 256 gfx::Rect viewport, 257 gfx::Rect clip, 258 bool hardware) { 259 DCHECK(!invoking_composite_); 260 base::AutoReset<bool> invoking_composite_resetter(&invoking_composite_, true); 261 did_swap_buffer_ = false; 262 263 gfx::Transform adjusted_transform = transform; 264 AdjustTransform(&adjusted_transform, viewport, clip); 265 SetExternalDrawConstraints(adjusted_transform, viewport, clip, hardware); 266 SetNeedsRedrawRect(gfx::Rect(viewport.size())); 267 268 if (needs_begin_frame_) 269 BeginFrame(cc::BeginFrameArgs::CreateForSynchronousCompositor()); 270 271 if (hardware) { 272 cached_hw_transform_ = adjusted_transform; 273 cached_hw_viewport_ = viewport; 274 cached_hw_clip_ = clip; 275 } else { 276 SetExternalDrawConstraints( 277 cached_hw_transform_, cached_hw_viewport_, cached_hw_clip_, true); 278 } 279 280 if (did_swap_buffer_) 281 OnSwapBuffersComplete(NULL); 282} 283 284void SynchronousCompositorOutputSurface::PostCheckForRetroactiveBeginFrame() { 285 // Synchronous compositor cannot perform retroactive begin frames, so 286 // intentionally no-op here. 287} 288 289// Not using base::NonThreadSafe as we want to enforce a more exacting threading 290// requirement: SynchronousCompositorOutputSurface() must only be used on the UI 291// thread. 292bool SynchronousCompositorOutputSurface::CalledOnValidThread() const { 293 return BrowserThread::CurrentlyOn(BrowserThread::UI); 294} 295 296SynchronousCompositorOutputSurfaceDelegate* 297SynchronousCompositorOutputSurface::GetDelegate() { 298 return SynchronousCompositorImpl::FromRoutingID(routing_id_); 299} 300 301} // namespace content 302