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