#include "chrome/browser/vr/vr_browser_renderer_thread.h"
#include <vector>
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "chrome/browser/vr/browser_renderer.h"
#include "chrome/browser/vr/ui.h"
#include "ui/gfx/geometry/quaternion.h"
#if BUILDFLAG(IS_WIN)
#include "chrome/browser/vr/graphics_delegate_win.h"
#endif
#undef DrawState
namespace {
constexpr base::TimeDelta kWebVrInitialFrameTimeout = base::Seconds(5);
constexpr base::TimeDelta kWebVrSpinnerTimeout = base::Seconds(2);
constexpr float kEpsilon = 0.1f;
constexpr float kMaxPosition = 1000000;
constexpr float kMinPosition = -kMaxPosition;
bool g_overlay_ui_disabled_for_testing_ = false;
bool InRange(float val, float min = kMinPosition, float max = kMaxPosition) {
return val > min && val < max;
}
}
namespace vr {
VRBrowserRendererThread* VRBrowserRendererThread::instance_for_testing_ =
nullptr;
VRBrowserRendererThread::VRBrowserRendererThread(
mojo::PendingRemote<device::mojom::ImmersiveOverlay> overlay,
const std::vector<device::mojom::XRViewPtr>& views)
: overlay_(std::move(overlay)),
task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {
DCHECK(instance_for_testing_ == nullptr);
instance_for_testing_ = this;
for (auto& view : views) {
if (view->eye == device::mojom::XREye::kLeft ||
view->eye == device::mojom::XREye::kRight) {
default_views_.push_back(view.Clone());
}
}
StartWebXrTimeout();
}
VRBrowserRendererThread::~VRBrowserRendererThread() {
StopWebXrTimeout();
StopOverlay();
instance_for_testing_ = nullptr;
}
void VRBrowserRendererThread::StopOverlay() {
browser_renderer_ = nullptr;
started_ = false;
graphics_ = nullptr;
ui_ = nullptr;
scheduler_ui_ = nullptr;
}
void VRBrowserRendererThread::StartWebXrTimeout() {
if (g_overlay_ui_disabled_for_testing_) {
return;
}
frame_timeout_running_ = true;
overlay_->SetOverlayAndWebXRVisibility(draw_state_.ShouldDrawUI(),
draw_state_.ShouldDrawWebXR());
if (!waiting_for_webxr_frame_) {
waiting_for_webxr_frame_ = true;
overlay_->RequestNotificationOnWebXrSubmitted(base::BindOnce(
&VRBrowserRendererThread::OnWebXRSubmitted, base::Unretained(this)));
}
webxr_spinner_timeout_closure_.Reset(base::BindOnce(
&VRBrowserRendererThread::OnWebXrTimeoutImminent,
base::Unretained(
this)));
task_runner_->PostDelayedTask(FROM_HERE,
webxr_spinner_timeout_closure_.callback(),
kWebVrSpinnerTimeout);
webxr_frame_timeout_closure_.Reset(base::BindOnce(
&VRBrowserRendererThread::OnWebXrTimedOut,
base::Unretained(
this)));
task_runner_->PostDelayedTask(FROM_HERE,
webxr_frame_timeout_closure_.callback(),
kWebVrInitialFrameTimeout);
}
void VRBrowserRendererThread::StopWebXrTimeout() {
if (g_overlay_ui_disabled_for_testing_) {
return;
}
if (!webxr_spinner_timeout_closure_.IsCancelled())
webxr_spinner_timeout_closure_.Cancel();
if (!webxr_frame_timeout_closure_.IsCancelled())
webxr_frame_timeout_closure_.Cancel();
OnSpinnerVisibilityChanged(false);
frame_timeout_running_ = false;
}
int VRBrowserRendererThread::GetNextRequestId() {
current_request_id_++;
if (current_request_id_ >= 0x10000)
current_request_id_ = 0;
return current_request_id_;
}
void VRBrowserRendererThread::OnWebXrTimeoutImminent() {
OnSpinnerVisibilityChanged(true);
if (scheduler_ui_) {
scheduler_ui_->OnWebXrTimeoutImminent();
}
}
void VRBrowserRendererThread::OnWebXrTimedOut() {
OnSpinnerVisibilityChanged(true);
if (scheduler_ui_) {
scheduler_ui_->OnWebXrTimedOut();
}
}
void VRBrowserRendererThread::UpdateOverlayState() {
if (draw_state_.ShouldDrawUI()) {
StartOverlay();
}
if (!g_overlay_ui_disabled_for_testing_) {
overlay_->SetOverlayAndWebXRVisibility(draw_state_.ShouldDrawUI(),
draw_state_.ShouldDrawWebXR());
}
if (draw_state_.ShouldDrawUI()) {
if (!g_overlay_ui_disabled_for_testing_) {
if (!graphics_) {
pending_overlay_update_ =
base::BindOnce(&VRBrowserRendererThread::UpdateOverlayState,
base::Unretained(this));
return;
}
overlay_->RequestNextOverlayPose(
base::BindOnce(&VRBrowserRendererThread::OnPose,
base::Unretained(this), GetNextRequestId()));
}
} else {
StopOverlay();
}
}
void VRBrowserRendererThread::SetFramesThrottled(bool throttled) {
if (frames_throttled_ == throttled)
return;
frames_throttled_ = throttled;
if (g_overlay_ui_disabled_for_testing_) {
return;
}
if (!waiting_for_webxr_frame_)
return;
if (frames_throttled_) {
StopWebXrTimeout();
OnWebXrTimeoutImminent();
} else {
StartWebXrTimeout();
}
}
void VRBrowserRendererThread::SetVisibleExternalPromptNotification(
ExternalPromptNotificationType prompt) {
if (!draw_state_.SetPrompt(prompt))
return;
UpdateOverlayState();
if (!ui_) {
if (prompt != ExternalPromptNotificationType::kPromptNone) {
DCHECK(started_);
}
return;
}
ui_->SetVisibleExternalPromptNotification(prompt);
}
void VRBrowserRendererThread::SetIndicatorsVisible(bool visible) {
if (draw_state_.SetIndicatorsVisible(visible))
UpdateOverlayState();
}
void VRBrowserRendererThread::OnSpinnerVisibilityChanged(bool visible) {
if (draw_state_.SetSpinnerVisible(visible))
UpdateOverlayState();
}
void VRBrowserRendererThread::SetCapturingState(
const CapturingStateModel& active_capturing,
const CapturingStateModel& background_capturing,
const CapturingStateModel& potential_capturing) {
if (ui_)
ui_->SetCapturingState(active_capturing, background_capturing,
potential_capturing);
}
VRBrowserRendererThread*
VRBrowserRendererThread::GetInstanceForTesting() {
return instance_for_testing_;
}
BrowserRenderer* VRBrowserRendererThread::GetBrowserRendererForTesting() {
return browser_renderer_.get();
}
namespace {
constexpr unsigned kSlidingAverageSize = 5;
}
void VRBrowserRendererThread::DisableOverlayForTesting() {
g_overlay_ui_disabled_for_testing_ = true;
}
void VRBrowserRendererThread::StartOverlay() {
if (started_)
return;
started_ = true;
std::unique_ptr<GraphicsDelegate> graphics = GraphicsDelegate::Create();
auto* initializing_graphics = graphics.get();
initializing_graphics->Initialize(
base::BindOnce(&VRBrowserRendererThread::OnGraphicsReady,
weak_ptr_factory_.GetWeakPtr(), std::move(graphics)));
}
void VRBrowserRendererThread::OnGraphicsReady(
std::unique_ptr<GraphicsDelegate> initializing_graphics) {
DVLOG(2) << __func__;
graphics_ = initializing_graphics.get();
DCHECK(!default_views_.empty());
graphics_->SetXrViews(default_views_);
graphics_->BindContext();
std::unique_ptr<Ui> ui = std::make_unique<Ui>();
static_cast<UiInterface*>(ui.get())->OnGlInitialized();
ui_ = static_cast<BrowserUiInterface*>(ui.get());
scheduler_ui_ = static_cast<UiInterface*>(ui.get())->GetSchedulerUiPtr();
if (draw_state_.GetPrompt() != ExternalPromptNotificationType::kPromptNone) {
ui_->SetVisibleExternalPromptNotification(draw_state_.GetPrompt());
}
browser_renderer_ = std::make_unique<BrowserRenderer>(
std::move(ui), std::move(initializing_graphics), kSlidingAverageSize);
graphics_->ClearContext();
if (pending_overlay_update_) {
std::move(pending_overlay_update_).Run();
}
}
void VRBrowserRendererThread::OnWebXRSubmitted() {
waiting_for_webxr_frame_ = false;
if (scheduler_ui_)
scheduler_ui_->OnWebXrFrameAvailable();
StopWebXrTimeout();
}
device::mojom::XRRenderInfoPtr ValidateFrameData(
device::mojom::XRRenderInfoPtr data) {
device::mojom::XRRenderInfoPtr ret = device::mojom::XRRenderInfo::New();
ret->mojo_from_viewer = device::mojom::VRPose::New();
if (data->mojo_from_viewer) {
if (data->mojo_from_viewer->orientation) {
if (abs(data->mojo_from_viewer->orientation->Length() - 1) < kEpsilon) {
ret->mojo_from_viewer->orientation =
data->mojo_from_viewer->orientation->Normalized();
}
}
if (data->mojo_from_viewer->position) {
ret->mojo_from_viewer->position = data->mojo_from_viewer->position;
bool any_out_of_range = !(InRange(ret->mojo_from_viewer->position->x()) &&
InRange(ret->mojo_from_viewer->position->y()) &&
InRange(ret->mojo_from_viewer->position->z()));
if (any_out_of_range) {
ret->mojo_from_viewer->position = std::nullopt;
DCHECK(false);
}
}
}
if (!ret->mojo_from_viewer->orientation) {
ret->mojo_from_viewer->orientation = gfx::Quaternion();
}
if (!ret->mojo_from_viewer->position) {
ret->mojo_from_viewer->position = gfx::Point3F();
}
ret->views.resize(data->views.size());
for (size_t i = 0; i < data->views.size(); i++) {
ret->views[i] = std::move(data->views[i]);
}
ret->frame_id = data->frame_id;
return ret;
}
void VRBrowserRendererThread::OnPose(int request_id,
device::mojom::XRRenderInfoPtr data) {
if (request_id != current_request_id_) {
return;
}
if (!draw_state_.ShouldDrawUI()) {
overlay_->SetOverlayAndWebXRVisibility(draw_state_.ShouldDrawUI(),
draw_state_.ShouldDrawWebXR());
if (graphics_)
graphics_->ResetMemoryBuffer();
return;
}
data = ValidateFrameData(std::move(data));
DCHECK(graphics_);
graphics_->SetXrViews(std::move(data->views));
if (!PreRender())
return;
DCHECK(data);
DCHECK(data->mojo_from_viewer);
DCHECK(data->mojo_from_viewer->orientation);
DCHECK(data->mojo_from_viewer->position);
const gfx::Point3F& pos = *data->mojo_from_viewer->position;
gfx::Transform head_from_unoriented_head(
data->mojo_from_viewer->orientation->inverse());
gfx::Transform unoriented_head_from_world;
unoriented_head_from_world.Translate3d(-pos.x(), -pos.y(), -pos.z());
gfx::Transform head_from_world =
head_from_unoriented_head * unoriented_head_from_world;
base::TimeTicks now = base::TimeTicks::Now();
bool need_submit = false;
if (draw_state_.ShouldDrawWebXR()) {
browser_renderer_->DrawWebXrFrame(now, head_from_world);
need_submit = true;
} else if (draw_state_.ShouldDrawUI()) {
browser_renderer_->DrawBrowserFrame(now, head_from_world);
need_submit = true;
}
if (need_submit) {
SubmitFrame(data->frame_id);
}
}
bool VRBrowserRendererThread::PreRender() {
if (!graphics_->PreRender()) {
StopOverlay();
StartOverlay();
if (!graphics_) {
pending_overlay_update_ =
base::BindOnce(&VRBrowserRendererThread::UpdateOverlayState,
base::Unretained(this));
return false;
}
return graphics_->PreRender();
}
return true;
}
void VRBrowserRendererThread::SubmitFrame(int16_t frame_id) {
DVLOG(3) << __func__ << " frame_id=" << frame_id;
graphics_->PostRender();
overlay_->SubmitOverlayTexture(
frame_id, graphics_->GetTexture(), graphics_->GetSyncToken(),
graphics_->GetLeft(), graphics_->GetRight(),
base::BindOnce(&VRBrowserRendererThread::SubmitResult,
base::Unretained(this)));
}
void VRBrowserRendererThread::SubmitResult(bool success) {
DVLOG(3) << __func__ << " success=" << success;
if (!success && graphics_) {
graphics_->ResetMemoryBuffer();
}
if (scheduler_ui_ && success && !frame_timeout_running_) {
scheduler_ui_->OnWebXrFrameAvailable();
}
if (draw_state_.ShouldDrawUI() && started_) {
DVLOG(3) << __func__ << " Requesting Overlay Pose";
overlay_->RequestNextOverlayPose(
base::BindOnce(&VRBrowserRendererThread::OnPose,
base::Unretained(this), GetNextRequestId()));
}
}
bool VRBrowserRendererThread::DrawState::ShouldDrawUI() {
return prompt_ != ExternalPromptNotificationType::kPromptNone ||
spinner_visible_ || indicators_visible_;
}
bool VRBrowserRendererThread::DrawState::ShouldDrawWebXR() {
return ((prompt_ == ExternalPromptNotificationType::kPromptNone ||
indicators_visible_) &&
!spinner_visible_);
}
bool VRBrowserRendererThread::DrawState::SetPrompt(
ExternalPromptNotificationType prompt) {
bool old_ui = ShouldDrawUI();
bool old_webxr = ShouldDrawWebXR();
prompt_ = prompt;
return old_ui != ShouldDrawUI() || old_webxr != ShouldDrawWebXR();
}
bool VRBrowserRendererThread::DrawState::SetSpinnerVisible(bool visible) {
bool old_ui = ShouldDrawUI();
bool old_webxr = ShouldDrawWebXR();
spinner_visible_ = visible;
return old_ui != ShouldDrawUI() || old_webxr != ShouldDrawWebXR();
}
bool VRBrowserRendererThread::DrawState::SetIndicatorsVisible(bool visible) {
bool old_ui = ShouldDrawUI();
bool old_webxr = ShouldDrawWebXR();
indicators_visible_ = visible;
return old_ui != ShouldDrawUI() || old_webxr != ShouldDrawWebXR();
}
}