Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(267)

Side by Side Diff: chrome/browser/android/vr_shell/vr_shell.cc

Issue 2729523002: Re-land^2 WebVR compositor bypass via BrowserMain context + mailbox (Closed)
Patch Set: Rebase to 11e28fd6b9380b77273db51ef0b6ccc7ea341944 Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/android/vr_shell/vr_shell.h" 5 #include "chrome/browser/android/vr_shell/vr_shell.h"
6 6
7 #include <android/native_window_jni.h> 7 #include <android/native_window_jni.h>
8 8
9 #include <string> 9 #include <string>
10 #include <utility> 10 #include <utility>
(...skipping 18 matching lines...) Expand all
29 #include "chrome/browser/android/vr_shell/vr_web_contents_observer.h" 29 #include "chrome/browser/android/vr_shell/vr_web_contents_observer.h"
30 #include "content/public/browser/navigation_controller.h" 30 #include "content/public/browser/navigation_controller.h"
31 #include "content/public/browser/render_view_host.h" 31 #include "content/public/browser/render_view_host.h"
32 #include "content/public/browser/render_widget_host.h" 32 #include "content/public/browser/render_widget_host.h"
33 #include "content/public/browser/render_widget_host_view.h" 33 #include "content/public/browser/render_widget_host_view.h"
34 #include "content/public/browser/web_contents.h" 34 #include "content/public/browser/web_contents.h"
35 #include "content/public/common/content_features.h" 35 #include "content/public/common/content_features.h"
36 #include "content/public/common/referrer.h" 36 #include "content/public/common/referrer.h"
37 #include "device/vr/android/gvr/gvr_device.h" 37 #include "device/vr/android/gvr/gvr_device.h"
38 #include "device/vr/android/gvr/gvr_device_provider.h" 38 #include "device/vr/android/gvr/gvr_device_provider.h"
39 #include "gpu/command_buffer/common/mailbox.h"
39 #include "jni/VrShellImpl_jni.h" 40 #include "jni/VrShellImpl_jni.h"
40 #include "third_party/WebKit/public/platform/WebInputEvent.h" 41 #include "third_party/WebKit/public/platform/WebInputEvent.h"
41 #include "ui/android/view_android.h" 42 #include "ui/android/view_android.h"
42 #include "ui/android/window_android.h" 43 #include "ui/android/window_android.h"
43 #include "ui/base/page_transition_types.h" 44 #include "ui/base/page_transition_types.h"
44 #include "ui/display/display.h" 45 #include "ui/display/display.h"
45 #include "ui/display/screen.h" 46 #include "ui/display/screen.h"
46 #include "ui/gfx/transform.h" 47 #include "ui/gfx/transform.h"
47 #include "ui/gfx/transform_util.h" 48 #include "ui/gfx/transform_util.h"
48 49
49 using base::android::JavaParamRef; 50 using base::android::JavaParamRef;
50 using base::android::JavaRef; 51 using base::android::JavaRef;
51 52
52 namespace vr_shell { 53 namespace vr_shell {
53 54
54 namespace { 55 namespace {
55 vr_shell::VrShell* g_instance; 56 vr_shell::VrShell* g_instance;
56 57
57 static const char kVrShellUIURL[] = "chrome://vr-shell-ui"; 58 static const char kVrShellUIURL[] = "chrome://vr-shell-ui";
58 59
60 // Default downscale factor for computing the recommended WebVR
61 // renderWidth/Height from the 1:1 pixel mapped size. Using a rather
62 // aggressive downscale due to the high overhead of copying pixels
63 // twice before handing off to GVR. For comparison, the polyfill
64 // uses approximately 0.55 on a Pixel XL.
65 static constexpr float kWebVrRecommendedResolutionScale = 0.5;
66
59 void SetIsInVR(content::WebContents* contents, bool is_in_vr) { 67 void SetIsInVR(content::WebContents* contents, bool is_in_vr) {
60 if (contents && contents->GetRenderWidgetHostView()) 68 if (contents && contents->GetRenderWidgetHostView())
61 contents->GetRenderWidgetHostView()->SetIsInVR(is_in_vr); 69 contents->GetRenderWidgetHostView()->SetIsInVR(is_in_vr);
62 } 70 }
63 71
64 } // namespace 72 } // namespace
65 73
66 VrShell::VrShell(JNIEnv* env, 74 VrShell::VrShell(JNIEnv* env,
67 jobject obj, 75 jobject obj,
68 ui::WindowAndroid* content_window, 76 ui::WindowAndroid* content_window,
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
163 ui_contents_->GetController().LoadURL( 171 ui_contents_->GetController().LoadURL(
164 url, content::Referrer(), 172 url, content::Referrer(),
165 ui::PageTransition::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string("")); 173 ui::PageTransition::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string(""));
166 } 174 }
167 175
168 bool RegisterVrShell(JNIEnv* env) { 176 bool RegisterVrShell(JNIEnv* env) {
169 return RegisterNativesImpl(env); 177 return RegisterNativesImpl(env);
170 } 178 }
171 179
172 VrShell::~VrShell() { 180 VrShell::~VrShell() {
181 delegate_provider_->RemoveDelegate();
173 { 182 {
174 // The GvrLayout is, and must always be, used only on the UI thread, and the 183 // The GvrLayout is, and must always be, used only on the UI thread, and the
175 // GvrApi used for rendering should only be used from the GL thread as it's 184 // GvrApi used for rendering should only be used from the GL thread as it's
176 // not thread safe. However, the GvrLayout owns the GvrApi instance, and 185 // not thread safe. However, the GvrLayout owns the GvrApi instance, and
177 // when it gets shut down it deletes the GvrApi instance with it. Therefore, 186 // when it gets shut down it deletes the GvrApi instance with it. Therefore,
178 // we need to block shutting down the GvrLayout on stopping our GL thread 187 // we need to block shutting down the GvrLayout on stopping our GL thread
179 // from using the GvrApi instance. 188 // from using the GvrApi instance.
180 // base::Thread::Stop, which is called when destroying the thread, asserts 189 // base::Thread::Stop, which is called when destroying the thread, asserts
181 // that IO is allowed to prevent jank, but there shouldn't be any concerns 190 // that IO is allowed to prevent jank, but there shouldn't be any concerns
182 // regarding jank in this case, because we're switching from 3D to 2D, 191 // regarding jank in this case, because we're switching from 3D to 2D,
183 // adding/removing a bunch of Java views, and probably changing device 192 // adding/removing a bunch of Java views, and probably changing device
184 // orientation here. 193 // orientation here.
185 base::ThreadRestrictions::ScopedAllowIO allow_io; 194 base::ThreadRestrictions::ScopedAllowIO allow_io;
186 gl_thread_.reset(); 195 gl_thread_.reset();
187 } 196 }
188 delegate_provider_->RemoveDelegate();
189 g_instance = nullptr; 197 g_instance = nullptr;
190 } 198 }
191 199
192 void VrShell::PostToGlThreadWhenReady(const base::Closure& task) { 200 void VrShell::PostToGlThreadWhenReady(const base::Closure& task) {
193 // TODO(mthiesse): Remove this blocking wait. Queue up events if thread isn't 201 // TODO(mthiesse): Remove this blocking wait. Queue up events if thread isn't
194 // finished starting? 202 // finished starting?
195 gl_thread_->WaitUntilThreadStarted(); 203 gl_thread_->WaitUntilThreadStarted();
196 gl_thread_->task_runner()->PostTask(FROM_HERE, task); 204 gl_thread_->task_runner()->PostTask(FROM_HERE, task);
197 } 205 }
198 206
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
264 } 272 }
265 273
266 void VrShell::SetWebVrMode(JNIEnv* env, 274 void VrShell::SetWebVrMode(JNIEnv* env,
267 const base::android::JavaParamRef<jobject>& obj, 275 const base::android::JavaParamRef<jobject>& obj,
268 bool enabled) { 276 bool enabled) {
269 webvr_mode_ = enabled; 277 webvr_mode_ = enabled;
270 if (metrics_helper_) 278 if (metrics_helper_)
271 metrics_helper_->SetWebVREnabled(enabled); 279 metrics_helper_->SetWebVREnabled(enabled);
272 PostToGlThreadWhenReady(base::Bind(&VrShellGl::SetWebVrMode, 280 PostToGlThreadWhenReady(base::Bind(&VrShellGl::SetWebVrMode,
273 gl_thread_->GetVrShellGl(), enabled)); 281 gl_thread_->GetVrShellGl(), enabled));
282
274 html_interface_->SetMode(enabled ? UiInterface::Mode::WEB_VR 283 html_interface_->SetMode(enabled ? UiInterface::Mode::WEB_VR
275 : UiInterface::Mode::STANDARD); 284 : UiInterface::Mode::STANDARD);
276 } 285 }
277 286
278 void VrShell::OnLoadProgressChanged(JNIEnv* env, 287 void VrShell::OnLoadProgressChanged(JNIEnv* env,
279 const JavaParamRef<jobject>& obj, 288 const JavaParamRef<jobject>& obj,
280 double progress) { 289 double progress) {
281 html_interface_->SetLoadProgress(progress); 290 html_interface_->SetLoadProgress(progress);
282 } 291 }
283 292
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
317 jboolean incognito, 326 jboolean incognito,
318 jint id) { 327 jint id) {
319 html_interface_->RemoveTab(incognito, id); 328 html_interface_->RemoveTab(incognito, id);
320 } 329 }
321 330
322 void VrShell::SetWebVRSecureOrigin(bool secure_origin) { 331 void VrShell::SetWebVRSecureOrigin(bool secure_origin) {
323 // TODO(cjgrant): Align this state with the logic that drives the omnibox. 332 // TODO(cjgrant): Align this state with the logic that drives the omnibox.
324 html_interface_->SetWebVRSecureOrigin(secure_origin); 333 html_interface_->SetWebVRSecureOrigin(secure_origin);
325 } 334 }
326 335
327 void VrShell::SubmitWebVRFrame() {} 336 void VrShell::SubmitWebVRFrame(int16_t frame_index,
337 const gpu::MailboxHolder& mailbox) {
338 TRACE_EVENT1("gpu", "SubmitWebVRFrame", "frame", frame_index);
339
340 PostToGlThreadWhenReady(base::Bind(&VrShellGl::SubmitWebVRFrame,
341 gl_thread_->GetVrShellGl(), frame_index,
342 mailbox));
343 }
328 344
329 void VrShell::UpdateWebVRTextureBounds(int16_t frame_index, 345 void VrShell::UpdateWebVRTextureBounds(int16_t frame_index,
330 const gvr::Rectf& left_bounds, 346 const gvr::Rectf& left_bounds,
331 const gvr::Rectf& right_bounds) { 347 const gvr::Rectf& right_bounds,
348 const gvr::Sizei& source_size) {
332 PostToGlThreadWhenReady(base::Bind(&VrShellGl::UpdateWebVRTextureBounds, 349 PostToGlThreadWhenReady(base::Bind(&VrShellGl::UpdateWebVRTextureBounds,
333 gl_thread_->GetVrShellGl(), frame_index, 350 gl_thread_->GetVrShellGl(), frame_index,
334 left_bounds, right_bounds)); 351 left_bounds, right_bounds, source_size));
335 } 352 }
336 353
337 bool VrShell::SupportsPresentation() { 354 bool VrShell::SupportsPresentation() {
338 return true; 355 return true;
339 } 356 }
340 357
341 void VrShell::ResetPose() { 358 void VrShell::ResetPose() {
342 gl_thread_->task_runner()->PostTask( 359 gl_thread_->task_runner()->PostTask(
343 FROM_HERE, base::Bind(&VrShellGl::ResetPose, gl_thread_->GetVrShellGl())); 360 FROM_HERE, base::Bind(&VrShellGl::ResetPose, gl_thread_->GetVrShellGl()));
344 } 361 }
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
378 } 395 }
379 396
380 void VrShell::ContentSurfaceChanged(jobject surface) { 397 void VrShell::ContentSurfaceChanged(jobject surface) {
381 content_surface_ = surface; 398 content_surface_ = surface;
382 content_compositor_->SurfaceChanged(surface); 399 content_compositor_->SurfaceChanged(surface);
383 JNIEnv* env = base::android::AttachCurrentThread(); 400 JNIEnv* env = base::android::AttachCurrentThread();
384 Java_VrShellImpl_contentSurfaceChanged(env, j_vr_shell_.obj()); 401 Java_VrShellImpl_contentSurfaceChanged(env, j_vr_shell_.obj());
385 } 402 }
386 403
387 void VrShell::GvrDelegateReady() { 404 void VrShell::GvrDelegateReady() {
405 PostToGlThreadWhenReady(base::Bind(
406 &VrShellGl::SetSubmitClient, gl_thread_->GetVrShellGl(),
407 base::Passed(
408 delegate_provider_->TakeSubmitFrameClient().PassInterface())));
388 delegate_provider_->SetDelegate(this, gvr_api_); 409 delegate_provider_->SetDelegate(this, gvr_api_);
389 } 410 }
390 411
391 void VrShell::AppButtonPressed() { 412 void VrShell::AppButtonPressed() {
392 if (vr_shell_enabled_) 413 if (vr_shell_enabled_)
393 html_interface_->HandleAppButtonClicked(); 414 html_interface_->HandleAppButtonClicked();
394 } 415 }
395 416
396 void VrShell::ContentPhysicalBoundsChanged(JNIEnv* env, 417 void VrShell::ContentPhysicalBoundsChanged(JNIEnv* env,
397 const JavaParamRef<jobject>& object, 418 const JavaParamRef<jobject>& object,
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after
592 613
593 void VrShell::ProcessContentGesture( 614 void VrShell::ProcessContentGesture(
594 std::unique_ptr<blink::WebInputEvent> event) { 615 std::unique_ptr<blink::WebInputEvent> event) {
595 if (content_input_manager_) { 616 if (content_input_manager_) {
596 content_input_manager_->ProcessUpdatedGesture(std::move(event)); 617 content_input_manager_->ProcessUpdatedGesture(std::move(event));
597 } else if (android_ui_gesture_target_) { 618 } else if (android_ui_gesture_target_) {
598 android_ui_gesture_target_->DispatchWebInputEvent(std::move(event)); 619 android_ui_gesture_target_->DispatchWebInputEvent(std::move(event));
599 } 620 }
600 } 621 }
601 622
623 /* static */
602 device::mojom::VRPosePtr VrShell::VRPosePtrFromGvrPose(gvr::Mat4f head_mat) { 624 device::mojom::VRPosePtr VrShell::VRPosePtrFromGvrPose(gvr::Mat4f head_mat) {
603 device::mojom::VRPosePtr pose = device::mojom::VRPose::New(); 625 device::mojom::VRPosePtr pose = device::mojom::VRPose::New();
604 626
605 pose->orientation.emplace(4); 627 pose->orientation.emplace(4);
606 628
607 gfx::Transform inv_transform( 629 gfx::Transform inv_transform(
608 head_mat.m[0][0], head_mat.m[0][1], head_mat.m[0][2], head_mat.m[0][3], 630 head_mat.m[0][0], head_mat.m[0][1], head_mat.m[0][2], head_mat.m[0][3],
609 head_mat.m[1][0], head_mat.m[1][1], head_mat.m[1][2], head_mat.m[1][3], 631 head_mat.m[1][0], head_mat.m[1][1], head_mat.m[1][2], head_mat.m[1][3],
610 head_mat.m[2][0], head_mat.m[2][1], head_mat.m[2][2], head_mat.m[2][3], 632 head_mat.m[2][0], head_mat.m[2][1], head_mat.m[2][2], head_mat.m[2][3],
611 head_mat.m[3][0], head_mat.m[3][1], head_mat.m[3][2], head_mat.m[3][3]); 633 head_mat.m[3][0], head_mat.m[3][1], head_mat.m[3][2], head_mat.m[3][3]);
(...skipping 10 matching lines...) Expand all
622 644
623 pose->position.emplace(3); 645 pose->position.emplace(3);
624 pose->position.value()[0] = decomposed_transform.translate[0]; 646 pose->position.value()[0] = decomposed_transform.translate[0];
625 pose->position.value()[1] = decomposed_transform.translate[1]; 647 pose->position.value()[1] = decomposed_transform.translate[1];
626 pose->position.value()[2] = decomposed_transform.translate[2]; 648 pose->position.value()[2] = decomposed_transform.translate[2];
627 } 649 }
628 650
629 return pose; 651 return pose;
630 } 652 }
631 653
654 /* static */
655 gvr::Sizei VrShell::GetRecommendedWebVrSize(gvr::GvrApi* gvr_api) {
656 // Pick a reasonable default size for the WebVR transfer surface
657 // based on a downscaled 1:1 render resolution. This size will also
658 // be reported to the client via CreateVRDisplayInfo as the
659 // client-recommended renderWidth/renderHeight and for the GVR
660 // framebuffer. If the client chooses a different size or resizes it
661 // while presenting, we'll resize the transfer surface and GVR
662 // framebuffer to match.
663 gvr::Sizei render_target_size =
664 gvr_api->GetMaximumEffectiveRenderTargetSize();
665 gvr::Sizei webvr_size = {static_cast<int>(render_target_size.width *
666 kWebVrRecommendedResolutionScale),
667 static_cast<int>(render_target_size.height *
668 kWebVrRecommendedResolutionScale)};
669 // Ensure that the width is an even number so that the eyes each
670 // get the same size, the recommended renderWidth is per eye
671 // and the client will use the sum of the left and right width.
672 //
673 // TODO(klausw,crbug.com/699350): should we round the recommended
674 // size to a multiple of 2^N pixels to be friendlier to the GPU? The
675 // exact size doesn't matter, and it might be more efficient.
676 webvr_size.width &= ~1;
677
678 return webvr_size;
679 }
680
681 /* static */
632 device::mojom::VRDisplayInfoPtr VrShell::CreateVRDisplayInfo( 682 device::mojom::VRDisplayInfoPtr VrShell::CreateVRDisplayInfo(
633 gvr::GvrApi* gvr_api, 683 gvr::GvrApi* gvr_api,
634 gvr::Sizei compositor_size, 684 gvr::Sizei recommended_size,
635 uint32_t device_id) { 685 uint32_t device_id) {
636 TRACE_EVENT0("input", "GvrDevice::GetVRDevice"); 686 TRACE_EVENT0("input", "GvrDevice::GetVRDevice");
637 687
638 device::mojom::VRDisplayInfoPtr device = device::mojom::VRDisplayInfo::New(); 688 device::mojom::VRDisplayInfoPtr device = device::mojom::VRDisplayInfo::New();
639 689
640 device->index = device_id; 690 device->index = device_id;
641 691
642 device->capabilities = device::mojom::VRDisplayCapabilities::New(); 692 device->capabilities = device::mojom::VRDisplayCapabilities::New();
643 device->capabilities->hasOrientation = true; 693 device->capabilities->hasOrientation = true;
644 device->capabilities->hasPosition = false; 694 device->capabilities->hasPosition = false;
645 device->capabilities->hasExternalDisplay = false; 695 device->capabilities->hasExternalDisplay = false;
646 device->capabilities->canPresent = true; 696 device->capabilities->canPresent = true;
647 697
648 std::string vendor = gvr_api->GetViewerVendor(); 698 std::string vendor = gvr_api->GetViewerVendor();
649 std::string model = gvr_api->GetViewerModel(); 699 std::string model = gvr_api->GetViewerModel();
650 device->displayName = vendor + " " + model; 700 device->displayName = vendor + " " + model;
651 701
652 gvr::BufferViewportList gvr_buffer_viewports = 702 gvr::BufferViewportList gvr_buffer_viewports =
653 gvr_api->CreateEmptyBufferViewportList(); 703 gvr_api->CreateEmptyBufferViewportList();
654 gvr_buffer_viewports.SetToRecommendedBufferViewports(); 704 gvr_buffer_viewports.SetToRecommendedBufferViewports();
655 705
656 device->leftEye = device::mojom::VREyeParameters::New(); 706 device->leftEye = device::mojom::VREyeParameters::New();
657 device->rightEye = device::mojom::VREyeParameters::New(); 707 device->rightEye = device::mojom::VREyeParameters::New();
658 for (auto eye : {GVR_LEFT_EYE, GVR_RIGHT_EYE}) { 708 for (auto eye : {GVR_LEFT_EYE, GVR_RIGHT_EYE}) {
659 device::mojom::VREyeParametersPtr& eye_params = 709 device::mojom::VREyeParametersPtr& eye_params =
660 (eye == GVR_LEFT_EYE) ? device->leftEye : device->rightEye; 710 (eye == GVR_LEFT_EYE) ? device->leftEye : device->rightEye;
661 eye_params->fieldOfView = device::mojom::VRFieldOfView::New(); 711 eye_params->fieldOfView = device::mojom::VRFieldOfView::New();
662 eye_params->offset.resize(3); 712 eye_params->offset.resize(3);
663 eye_params->renderWidth = compositor_size.width / 2; 713 eye_params->renderWidth = recommended_size.width / 2;
664 eye_params->renderHeight = compositor_size.height; 714 eye_params->renderHeight = recommended_size.height;
665 715
666 gvr::BufferViewport eye_viewport = gvr_api->CreateBufferViewport(); 716 gvr::BufferViewport eye_viewport = gvr_api->CreateBufferViewport();
667 gvr_buffer_viewports.GetBufferViewport(eye, &eye_viewport); 717 gvr_buffer_viewports.GetBufferViewport(eye, &eye_viewport);
668 gvr::Rectf eye_fov = eye_viewport.GetSourceFov(); 718 gvr::Rectf eye_fov = eye_viewport.GetSourceFov();
669 eye_params->fieldOfView->upDegrees = eye_fov.top; 719 eye_params->fieldOfView->upDegrees = eye_fov.top;
670 eye_params->fieldOfView->downDegrees = eye_fov.bottom; 720 eye_params->fieldOfView->downDegrees = eye_fov.bottom;
671 eye_params->fieldOfView->leftDegrees = eye_fov.left; 721 eye_params->fieldOfView->leftDegrees = eye_fov.left;
672 eye_params->fieldOfView->rightDegrees = eye_fov.right; 722 eye_params->fieldOfView->rightDegrees = eye_fov.right;
673 723
674 gvr::Mat4f eye_mat = gvr_api->GetEyeFromHeadMatrix(eye); 724 gvr::Mat4f eye_mat = gvr_api->GetEyeFromHeadMatrix(eye);
(...skipping 20 matching lines...) Expand all
695 jboolean reprojected_rendering) { 745 jboolean reprojected_rendering) {
696 return reinterpret_cast<intptr_t>(new VrShell( 746 return reinterpret_cast<intptr_t>(new VrShell(
697 env, obj, reinterpret_cast<ui::WindowAndroid*>(content_window_android), 747 env, obj, reinterpret_cast<ui::WindowAndroid*>(content_window_android),
698 content::WebContents::FromJavaWebContents(ui_web_contents), 748 content::WebContents::FromJavaWebContents(ui_web_contents),
699 reinterpret_cast<ui::WindowAndroid*>(ui_window_android), for_web_vr, 749 reinterpret_cast<ui::WindowAndroid*>(ui_window_android), for_web_vr,
700 VrShellDelegate::GetNativeVrShellDelegate(env, delegate), 750 VrShellDelegate::GetNativeVrShellDelegate(env, delegate),
701 reinterpret_cast<gvr_context*>(gvr_api), reprojected_rendering)); 751 reinterpret_cast<gvr_context*>(gvr_api), reprojected_rendering));
702 } 752 }
703 753
704 } // namespace vr_shell 754 } // namespace vr_shell
OLDNEW
« no previous file with comments | « chrome/browser/android/vr_shell/vr_shell.h ('k') | chrome/browser/android/vr_shell/vr_shell_delegate.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698