1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/webxr/mailbox_to_surface_bridge_impl.h"
6
7 #include <memory>
8 #include <string>
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/logging.h"
13 #include "base/system/sys_info.h"
14 #include "base/threading/sequenced_task_runner_handle.h"
15 #include "components/viz/common/gpu/context_provider.h"
16 #include "content/public/browser/android/compositor.h"
17 #include "content/public/browser/browser_task_traits.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "gpu/GLES2/gl2extchromium.h"
20 #include "gpu/command_buffer/client/context_support.h"
21 #include "gpu/command_buffer/client/gles2_interface.h"
22 #include "gpu/command_buffer/client/shared_image_interface.h"
23 #include "gpu/command_buffer/common/gpu_memory_buffer_support.h"
24 #include "gpu/command_buffer/common/mailbox.h"
25 #include "gpu/command_buffer/common/mailbox_holder.h"
26 #include "gpu/ipc/client/gpu_channel_host.h"
27 #include "gpu/ipc/common/gpu_memory_buffer_impl_android_hardware_buffer.h"
28 #include "gpu/ipc/common/gpu_surface_tracker.h"
29 #include "services/viz/public/cpp/gpu/context_provider_command_buffer.h"
30 #include "ui/gfx/color_space.h"
31 #include "ui/gfx/transform.h"
32 #include "ui/gl/android/surface_texture.h"
33
34 #include <android/native_window_jni.h>
35
36 #define VOID_OFFSET(x) reinterpret_cast<void*>(x)
37 #define SHADER(Src) #Src
38
39 namespace {
40
41 /* clang-format off */
42 const char kQuadCopyVertex[] = SHADER(
43 precision mediump float;
44 attribute vec4 a_Position;
45 attribute vec2 a_TexCoordinate;
46 varying highp vec2 v_TexCoordinate;
47 uniform mat4 u_UvTransform;
48 void main() {
49 highp vec4 uv_in = vec4(a_TexCoordinate.x, a_TexCoordinate.y, 0, 1);
50 v_TexCoordinate = (u_UvTransform * uv_in).xy;
51 gl_Position = a_Position;
52 }
53 );
54
55 const char kQuadCopyFragment[] = SHADER(
56 precision highp float;
57 uniform sampler2D u_Texture;
58 varying vec2 v_TexCoordinate;
59 void main() {
60 gl_FragColor = texture2D(u_Texture, v_TexCoordinate);
61 }
62 );
63
64 const float kQuadVertices[] = {
65 // x y u, v
66 -1.f, 1.f, 0.f, 1.f,
67 -1.f, -1.f, 0.f, 0.f,
68 1.f, -1.f, 1.f, 0.f,
69 1.f, 1.f, 1.f, 1.f};
70 /* clang-format on */
71
72 static constexpr int kQuadVerticesSize = sizeof(kQuadVertices);
73
CompileShader(gpu::gles2::GLES2Interface * gl,GLenum shader_type,const GLchar * shader_source)74 GLuint CompileShader(gpu::gles2::GLES2Interface* gl,
75 GLenum shader_type,
76 const GLchar* shader_source) {
77 GLuint shader_handle = gl->CreateShader(shader_type);
78 if (shader_handle != 0) {
79 // Pass in the shader source.
80 GLint len = strlen(shader_source);
81 gl->ShaderSource(shader_handle, 1, &shader_source, &len);
82 // Compile the shader.
83 gl->CompileShader(shader_handle);
84 // Get the compilation status.
85 GLint status = 0;
86 gl->GetShaderiv(shader_handle, GL_COMPILE_STATUS, &status);
87 if (status == GL_FALSE) {
88 GLint info_log_length = 0;
89 gl->GetShaderiv(shader_handle, GL_INFO_LOG_LENGTH, &info_log_length);
90 auto str_info_log = std::make_unique<GLchar[]>(info_log_length + 1);
91 gl->GetShaderInfoLog(shader_handle, info_log_length, nullptr,
92 str_info_log.get());
93 DLOG(ERROR) << "Error compiling shader: " << str_info_log.get();
94 gl->DeleteShader(shader_handle);
95 shader_handle = 0;
96 }
97 }
98
99 return shader_handle;
100 }
101
CreateAndLinkProgram(gpu::gles2::GLES2Interface * gl,GLuint vertex_shader_handle,GLuint fragment_shader_handle)102 GLuint CreateAndLinkProgram(gpu::gles2::GLES2Interface* gl,
103 GLuint vertex_shader_handle,
104 GLuint fragment_shader_handle) {
105 GLuint program_handle = gl->CreateProgram();
106
107 if (program_handle != 0) {
108 // Bind the vertex shader to the program.
109 gl->AttachShader(program_handle, vertex_shader_handle);
110
111 // Bind the fragment shader to the program.
112 gl->AttachShader(program_handle, fragment_shader_handle);
113
114 // Link the two shaders together into a program.
115 gl->LinkProgram(program_handle);
116
117 // Get the link status.
118 GLint link_status = 0;
119 gl->GetProgramiv(program_handle, GL_LINK_STATUS, &link_status);
120
121 // If the link failed, delete the program.
122 if (link_status == GL_FALSE) {
123 GLint info_log_length;
124 gl->GetProgramiv(program_handle, GL_INFO_LOG_LENGTH, &info_log_length);
125
126 auto str_info_log = std::make_unique<GLchar[]>(info_log_length + 1);
127 gl->GetProgramInfoLog(program_handle, info_log_length, nullptr,
128 str_info_log.get());
129 DLOG(ERROR) << "Error compiling program: " << str_info_log.get();
130 gl->DeleteProgram(program_handle);
131 program_handle = 0;
132 }
133 }
134
135 return program_handle;
136 }
137
ConsumeTexture(gpu::gles2::GLES2Interface * gl,const gpu::MailboxHolder & mailbox)138 GLuint ConsumeTexture(gpu::gles2::GLES2Interface* gl,
139 const gpu::MailboxHolder& mailbox) {
140 TRACE_EVENT0("gpu", "MailboxToSurfaceBridgeImpl::ConsumeTexture");
141 gl->WaitSyncTokenCHROMIUM(mailbox.sync_token.GetConstData());
142
143 return gl->CreateAndTexStorage2DSharedImageCHROMIUM(mailbox.mailbox.name);
144 }
145
146 } // namespace
147
148 namespace webxr {
149
MailboxToSurfaceBridgeImpl()150 MailboxToSurfaceBridgeImpl::MailboxToSurfaceBridgeImpl() {
151 DVLOG(1) << __FUNCTION__;
152 }
153
~MailboxToSurfaceBridgeImpl()154 MailboxToSurfaceBridgeImpl::~MailboxToSurfaceBridgeImpl() {
155 if (surface_handle_) {
156 // Unregister from the surface tracker to avoid a resource leak.
157 gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
158 tracker->RemoveSurface(surface_handle_);
159 }
160 DestroyContext();
161 DVLOG(1) << __FUNCTION__;
162 }
163
IsConnected()164 bool MailboxToSurfaceBridgeImpl::IsConnected() {
165 return context_provider_ && gl_ && context_support_;
166 }
167
IsGpuWorkaroundEnabled(int32_t workaround)168 bool MailboxToSurfaceBridgeImpl::IsGpuWorkaroundEnabled(int32_t workaround) {
169 DCHECK(IsConnected());
170
171 return context_provider_->GetGpuFeatureInfo().IsWorkaroundEnabled(workaround);
172 }
173
OnContextAvailableOnUiThread(scoped_refptr<viz::ContextProvider> provider)174 void MailboxToSurfaceBridgeImpl::OnContextAvailableOnUiThread(
175 scoped_refptr<viz::ContextProvider> provider) {
176 DVLOG(1) << __FUNCTION__;
177 // Must save a reference to the viz::ContextProvider to keep it alive,
178 // otherwise the GL context created from it becomes invalid on its
179 // destruction.
180 context_provider_ = std::move(provider);
181
182 DCHECK(on_context_bound_);
183 gl_thread_task_runner_->PostTask(
184 FROM_HERE,
185 base::BindOnce(
186 &MailboxToSurfaceBridgeImpl::BindContextProviderToCurrentThread,
187 base::Unretained(this)));
188 }
189
BindContextProviderToCurrentThread()190 void MailboxToSurfaceBridgeImpl::BindContextProviderToCurrentThread() {
191 auto result = context_provider_->BindToCurrentThread();
192 if (result != gpu::ContextResult::kSuccess) {
193 DLOG(ERROR) << "Failed to init viz::ContextProvider";
194 return;
195 }
196
197 gl_ = context_provider_->ContextGL();
198 context_support_ = context_provider_->ContextSupport();
199
200 if (!gl_) {
201 DLOG(ERROR) << "Did not get a GL context";
202 return;
203 }
204 if (!context_support_) {
205 DLOG(ERROR) << "Did not get a ContextSupport";
206 return;
207 }
208 InitializeRenderer();
209
210 DVLOG(1) << __FUNCTION__ << ": Context ready";
211 if (on_context_bound_) {
212 std::move(on_context_bound_).Run();
213 }
214 }
215
CreateSurface(gl::SurfaceTexture * surface_texture)216 void MailboxToSurfaceBridgeImpl::CreateSurface(
217 gl::SurfaceTexture* surface_texture) {
218 ANativeWindow* window = surface_texture->CreateSurface();
219 gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
220 ANativeWindow_acquire(window);
221 // Skip ANativeWindow_setBuffersGeometry, the default size appears to work.
222 surface_ = std::make_unique<gl::ScopedJavaSurface>(surface_texture);
223 surface_handle_ =
224 tracker->AddSurfaceForNativeWidget(gpu::GpuSurfaceTracker::SurfaceRecord(
225 window, surface_->j_surface(),
226 false /* can_be_used_with_surface_control */));
227 // Unregistering happens in the destructor.
228 ANativeWindow_release(window);
229 }
230
CreateAndBindContextProvider(base::OnceClosure on_bound_callback)231 void MailboxToSurfaceBridgeImpl::CreateAndBindContextProvider(
232 base::OnceClosure on_bound_callback) {
233 gl_thread_task_runner_ = base::ThreadTaskRunnerHandle::Get();
234 on_context_bound_ = std::move(on_bound_callback);
235
236 // The callback to run in this thread. It is necessary to keep |surface| alive
237 // until the context becomes available. So pass it on to the callback, so that
238 // it stays alive, and is destroyed on the same thread once done.
239 auto callback =
240 base::BindOnce(&MailboxToSurfaceBridgeImpl::OnContextAvailableOnUiThread,
241 weak_ptr_factory_.GetWeakPtr());
242
243 content::GetUIThreadTaskRunner({})->PostTask(
244 FROM_HERE, base::BindOnce(
245 [](int surface_handle,
246 content::Compositor::ContextProviderCallback callback) {
247 // Our attributes must be compatible with the shared
248 // offscreen surface used by virtualized contexts,
249 // otherwise mailbox synchronization doesn't work
250 // properly - it assumes a shared underlying GL context.
251 // See GetCompositorContextAttributes in
252 // content/browser/renderer_host/compositor_impl_android.cc
253 // and https://crbug.com/699330.
254 gpu::ContextCreationAttribs attributes;
255 attributes.alpha_size = -1;
256 attributes.red_size = 8;
257 attributes.green_size = 8;
258 attributes.blue_size = 8;
259 attributes.stencil_size = 0;
260 attributes.depth_size = 0;
261 attributes.samples = 0;
262 attributes.sample_buffers = 0;
263 attributes.bind_generates_resource = false;
264 if (base::SysInfo::IsLowEndDevice()) {
265 attributes.alpha_size = 0;
266 attributes.red_size = 5;
267 attributes.green_size = 6;
268 attributes.blue_size = 5;
269 }
270 content::Compositor::CreateContextProvider(
271 surface_handle, attributes,
272 gpu::SharedMemoryLimits::ForMailboxContext(),
273 std::move(callback));
274 },
275 surface_handle_, std::move(callback)));
276 }
277
ResizeSurface(int width,int height)278 void MailboxToSurfaceBridgeImpl::ResizeSurface(int width, int height) {
279 surface_width_ = width;
280 surface_height_ = height;
281
282 if (!IsConnected()) {
283 // We're not initialized yet, save the requested size for later.
284 needs_resize_ = true;
285 return;
286 }
287 DVLOG(1) << __FUNCTION__ << ": resize Surface to " << surface_width_ << "x"
288 << surface_height_;
289 gfx::ColorSpace color_space = gfx::ColorSpace::CreateSRGB();
290 gl_->ResizeCHROMIUM(surface_width_, surface_height_, 1.f,
291 color_space.AsGLColorSpace(), false);
292 gl_->Viewport(0, 0, surface_width_, surface_height_);
293 }
294
CopyMailboxToSurfaceAndSwap(const gpu::MailboxHolder & mailbox)295 bool MailboxToSurfaceBridgeImpl::CopyMailboxToSurfaceAndSwap(
296 const gpu::MailboxHolder& mailbox) {
297 return CopyMailboxToSurfaceAndSwap(mailbox, gfx::Transform());
298 }
299
CopyMailboxToSurfaceAndSwap(const gpu::MailboxHolder & mailbox,const gfx::Transform & uv_transform)300 bool MailboxToSurfaceBridgeImpl::CopyMailboxToSurfaceAndSwap(
301 const gpu::MailboxHolder& mailbox,
302 const gfx::Transform& uv_transform) {
303 if (!IsConnected()) {
304 // We may not have a context yet, i.e. due to surface initialization
305 // being incomplete. This is not an error, but we obviously can't draw
306 // yet. TODO(klausw): change the caller to defer this until we are ready.
307 return false;
308 }
309
310 TRACE_EVENT0("gpu", __FUNCTION__);
311
312 if (needs_resize_) {
313 ResizeSurface(surface_width_, surface_height_);
314 needs_resize_ = false;
315 }
316
317 DCHECK(mailbox.mailbox.IsSharedImage());
318
319 // While it's not an error to use a zero-sized Surface, it's not going to
320 // produce any visible output. Show a debug mode warning in that case to avoid
321 // another annoying debugging session.
322 DLOG_IF(WARNING, !surface_width_ || !surface_height_)
323 << "Surface is zero-sized. Missing call to ResizeSurface?";
324
325 GLuint sourceTexture = ConsumeTexture(gl_, mailbox);
326 gl_->BeginSharedImageAccessDirectCHROMIUM(
327 sourceTexture, GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
328 DrawQuad(sourceTexture, uv_transform);
329 gl_->EndSharedImageAccessDirectCHROMIUM(sourceTexture);
330 gl_->DeleteTextures(1, &sourceTexture);
331 gl_->SwapBuffers(swap_id_++);
332 return true;
333 }
334
GenSyncToken(gpu::SyncToken * out_sync_token)335 void MailboxToSurfaceBridgeImpl::GenSyncToken(gpu::SyncToken* out_sync_token) {
336 TRACE_EVENT0("gpu", __FUNCTION__);
337 DCHECK(IsConnected());
338 gl_->GenSyncTokenCHROMIUM(out_sync_token->GetData());
339 }
340
WaitSyncToken(const gpu::SyncToken & sync_token)341 void MailboxToSurfaceBridgeImpl::WaitSyncToken(
342 const gpu::SyncToken& sync_token) {
343 TRACE_EVENT0("gpu", __FUNCTION__);
344 DCHECK(IsConnected());
345 gl_->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
346 }
347
WaitForClientGpuFence(gfx::GpuFence * gpu_fence)348 void MailboxToSurfaceBridgeImpl::WaitForClientGpuFence(
349 gfx::GpuFence* gpu_fence) {
350 TRACE_EVENT0("gpu", __FUNCTION__);
351 DCHECK(IsConnected());
352 GLuint id = gl_->CreateClientGpuFenceCHROMIUM(gpu_fence->AsClientGpuFence());
353 gl_->WaitGpuFenceCHROMIUM(id);
354 gl_->DestroyGpuFenceCHROMIUM(id);
355 }
356
CreateGpuFence(const gpu::SyncToken & sync_token,base::OnceCallback<void (std::unique_ptr<gfx::GpuFence>)> callback)357 void MailboxToSurfaceBridgeImpl::CreateGpuFence(
358 const gpu::SyncToken& sync_token,
359 base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)> callback) {
360 TRACE_EVENT0("gpu", __FUNCTION__);
361 DCHECK(IsConnected());
362 gl_->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
363 GLuint id = gl_->CreateGpuFenceCHROMIUM();
364 context_support_->GetGpuFence(id, std::move(callback));
365 gl_->DestroyGpuFenceCHROMIUM(id);
366 }
367
CreateSharedImage(gpu::GpuMemoryBufferImplAndroidHardwareBuffer * buffer,const gfx::ColorSpace & color_space,uint32_t usage)368 gpu::MailboxHolder MailboxToSurfaceBridgeImpl::CreateSharedImage(
369 gpu::GpuMemoryBufferImplAndroidHardwareBuffer* buffer,
370 const gfx::ColorSpace& color_space,
371 uint32_t usage) {
372 TRACE_EVENT0("gpu", __FUNCTION__);
373 DCHECK(IsConnected());
374
375 auto* sii = context_provider_->SharedImageInterface();
376 DCHECK(sii);
377
378 gpu::MailboxHolder mailbox_holder;
379 mailbox_holder.mailbox = sii->CreateSharedImage(buffer, nullptr, color_space,
380 kTopLeft_GrSurfaceOrigin,
381 kPremul_SkAlphaType, usage);
382 mailbox_holder.sync_token = sii->GenVerifiedSyncToken();
383 DCHECK(!gpu::NativeBufferNeedsPlatformSpecificTextureTarget(
384 buffer->GetFormat()));
385 mailbox_holder.texture_target = GL_TEXTURE_2D;
386 return mailbox_holder;
387 }
388
DestroySharedImage(const gpu::MailboxHolder & mailbox_holder)389 void MailboxToSurfaceBridgeImpl::DestroySharedImage(
390 const gpu::MailboxHolder& mailbox_holder) {
391 TRACE_EVENT0("gpu", __FUNCTION__);
392 DCHECK(IsConnected());
393
394 auto* sii = context_provider_->SharedImageInterface();
395 DCHECK(sii);
396 sii->DestroySharedImage(mailbox_holder.sync_token, mailbox_holder.mailbox);
397 }
398
DestroyContext()399 void MailboxToSurfaceBridgeImpl::DestroyContext() {
400 gl_ = nullptr;
401 context_provider_ = nullptr;
402 }
403
InitializeRenderer()404 void MailboxToSurfaceBridgeImpl::InitializeRenderer() {
405 GLuint vertex_shader_handle =
406 CompileShader(gl_, GL_VERTEX_SHADER, kQuadCopyVertex);
407 if (!vertex_shader_handle) {
408 DestroyContext();
409 return;
410 }
411
412 GLuint fragment_shader_handle =
413 CompileShader(gl_, GL_FRAGMENT_SHADER, kQuadCopyFragment);
414 if (!fragment_shader_handle) {
415 DestroyContext();
416 return;
417 }
418
419 GLuint program_handle =
420 CreateAndLinkProgram(gl_, vertex_shader_handle, fragment_shader_handle);
421 if (!program_handle) {
422 DestroyContext();
423 return;
424 }
425
426 // Once the program is linked the shader objects are no longer needed
427 gl_->DeleteShader(vertex_shader_handle);
428 gl_->DeleteShader(fragment_shader_handle);
429
430 GLuint position_handle = gl_->GetAttribLocation(program_handle, "a_Position");
431 GLuint texCoord_handle =
432 gl_->GetAttribLocation(program_handle, "a_TexCoordinate");
433 GLuint texUniform_handle =
434 gl_->GetUniformLocation(program_handle, "u_Texture");
435 uniform_uv_transform_handle_ =
436 gl_->GetUniformLocation(program_handle, "u_UvTransform");
437
438 GLuint vertexBuffer = 0;
439 gl_->GenBuffers(1, &vertexBuffer);
440 gl_->BindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
441 gl_->BufferData(GL_ARRAY_BUFFER, kQuadVerticesSize, kQuadVertices,
442 GL_STATIC_DRAW);
443
444 // Set state once only, we assume that nobody else modifies GL state in a way
445 // that would interfere with our operations.
446 gl_->Disable(GL_CULL_FACE);
447 gl_->DepthMask(GL_FALSE);
448 gl_->Disable(GL_DEPTH_TEST);
449 gl_->Disable(GL_SCISSOR_TEST);
450 gl_->Disable(GL_BLEND);
451 gl_->Disable(GL_POLYGON_OFFSET_FILL);
452
453 // Not using gl_->Viewport, we assume that it defaults to the whole
454 // surface and gets updated by ResizeSurface externally as
455 // appropriate.
456
457 gl_->UseProgram(program_handle);
458
459 // Bind vertex attributes
460 gl_->BindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
461
462 gl_->EnableVertexAttribArray(position_handle);
463 gl_->EnableVertexAttribArray(texCoord_handle);
464
465 static constexpr size_t VERTEX_STRIDE = sizeof(float) * 4;
466 static constexpr size_t POSITION_ELEMENTS = 2;
467 static constexpr size_t TEXCOORD_ELEMENTS = 2;
468 static constexpr size_t POSITION_OFFSET = 0;
469 static constexpr size_t TEXCOORD_OFFSET = sizeof(float) * 2;
470
471 gl_->VertexAttribPointer(position_handle, POSITION_ELEMENTS, GL_FLOAT, false,
472 VERTEX_STRIDE, VOID_OFFSET(POSITION_OFFSET));
473 gl_->VertexAttribPointer(texCoord_handle, TEXCOORD_ELEMENTS, GL_FLOAT, false,
474 VERTEX_STRIDE, VOID_OFFSET(TEXCOORD_OFFSET));
475
476 gl_->ActiveTexture(GL_TEXTURE0);
477 gl_->Uniform1i(texUniform_handle, 0);
478 }
479
DrawQuad(unsigned int texture_handle,const gfx::Transform & uv_transform)480 void MailboxToSurfaceBridgeImpl::DrawQuad(unsigned int texture_handle,
481 const gfx::Transform& uv_transform) {
482 DCHECK(IsConnected());
483
484 // We're redrawing over the entire viewport, but it's generally more
485 // efficient on mobile tiling GPUs to clear anyway as a hint that
486 // we're done with the old content. TODO(klausw, https://crbug.com/700389):
487 // investigate using gl_->DiscardFramebufferEXT here since that's more
488 // efficient on desktop, but it would need a capability check since
489 // it's not supported on older devices such as Nexus 5X.
490 gl_->Clear(GL_COLOR_BUFFER_BIT);
491
492 float uv_transform_floats[16];
493 uv_transform.matrix().asColMajorf(uv_transform_floats);
494 gl_->UniformMatrix4fv(uniform_uv_transform_handle_, 1, GL_FALSE,
495 &uv_transform_floats[0]);
496
497 // Configure texture. This is a 1:1 pixel copy since the surface
498 // size is resized to match the source canvas, so we can use
499 // GL_NEAREST.
500 gl_->BindTexture(GL_TEXTURE_2D, texture_handle);
501 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
502 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
503 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
504 if (uv_transform.IsIdentity()) {
505 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
506 } else {
507 gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
508 }
509 gl_->DrawArrays(GL_TRIANGLE_FAN, 0, 4);
510 }
511
512 std::unique_ptr<device::MailboxToSurfaceBridge>
Create() const513 MailboxToSurfaceBridgeFactoryImpl::Create() const {
514 return std::make_unique<MailboxToSurfaceBridgeImpl>();
515 }
516
517 } // namespace webxr
518