Chromium Code Reviews| Index: device/vr/android/gvr/gvr_device.cc |
| diff --git a/device/vr/android/gvr/gvr_device.cc b/device/vr/android/gvr/gvr_device.cc |
| index 9dc6b39c8118bd81fb277020ed7047060d877c2d..a6505d082b495e357a0129f437bfd01561ff1abd 100644 |
| --- a/device/vr/android/gvr/gvr_device.cc |
| +++ b/device/vr/android/gvr/gvr_device.cc |
| @@ -21,14 +21,64 @@ namespace device { |
| namespace { |
| -static const uint64_t kPredictionTimeWithoutVsyncNanos = 50000000; |
| +// TODO(klausw): adjust this based on historic render times |
| +static const double kDefaultRenderTimeMillis = 15.0; |
| +static const double kPredictionTimeWithoutVsyncMillis = 24.0; |
| } // namespace |
| -GvrDevice::GvrDevice(GvrDeviceProvider* provider, GvrDelegate* delegate) |
| - : VRDevice(), delegate_(delegate), gvr_provider_(provider) {} |
| +// THIS IS A COPY, keep in sync with VRDisplay.cpp ? |
| +StatTracker::StatTracker(unsigned int capacity) : m_capacity(capacity) {} |
| -GvrDevice::~GvrDevice() {} |
| +StatTracker::~StatTracker() = default; |
| + |
| +void StatTracker::add(double item) { |
| + if (m_items.size() >= m_capacity) { |
| + m_items.pop_front(); |
| + } |
| + m_items.push_back(item); |
| +} |
| + |
| +void StatTracker::clear() { |
| + m_items.clear(); |
| +} |
| + |
| +bool StatTracker::hasPrediction() { |
| + return m_items.size() > 0; |
| +} |
| + |
| +double StatTracker::getPrediction() { |
| + assert(hasPrediction()); |
| + |
| + // If we have 3 or more items, ignore min and max outliers and |
| + // average the rest. For 2 or less, minmax.first and minmax.second |
| + // will both be m_items.end(), so it's just a plain average. |
| + auto minmax = m_items.size() > 2 ? |
| + std::minmax_element(m_items.begin(), m_items.end()) : |
| + std::minmax_element(m_items.end(), m_items.end()); |
| + |
| + double sum = 0.0; |
| + int count = 0; |
| + //VLOG(2) << __FUNCTION__ << ": stat start"; |
| + for (auto it = m_items.begin(); it != m_items.end(); ++it) { |
| + //VLOG(2) << __FUNCTION__ << ": val=" << *it; |
| + if (it == minmax.first || it == minmax.second) continue; |
| + sum += *it; |
| + ++count; |
| + } |
| + //VLOG(2) << __FUNCTION__ << ": stat return " << sum / count; |
| + return sum / count; |
| +} |
| +// COPY end |
| + |
| +GvrDevice::GvrDevice(GvrDeviceProvider* provider, GvrDelegate* delegate) |
| + : VRDevice(), delegate_(delegate), gvr_provider_(provider) { |
| + VLOG(1) << __FUNCTION__ << ": CONSTRUCTOR this=" << (void*)this << " ***********************************************************************************************************"; |
| +} |
| + |
| +GvrDevice::~GvrDevice() { |
| + VLOG(1) << __FUNCTION__ << ": DESTRUCTOR this=" << (void*)this << " ***********************************************************************************************************"; |
|
artem.bolgar
2017/02/14 05:04:24
It is possible that callback_map_ is not empty her
|
| +} |
| mojom::VRDisplayInfoPtr GvrDevice::GetVRDevice() { |
| TRACE_EVENT0("input", "GvrDevice::GetVRDevice"); |
| @@ -65,9 +115,12 @@ mojom::VRDisplayInfoPtr GvrDevice::GetVRDevice() { |
| right_eye->renderHeight = left_eye->renderHeight; |
| gvr::GvrApi* gvr_api = GetGvrApi(); |
| + VLOG(1) << __FUNCTION__ << ": gvr_api=" << gvr_api; |
| if (!gvr_api) { |
| // We may not be able to get an instance of GvrApi right away, so |
| // stub in some data till we have one. |
| + |
| + // TODO(klausw): I think we never get here ?! |
| device->displayName = "Unknown"; |
| left_eye->fieldOfView->upDegrees = 45; |
| @@ -88,30 +141,34 @@ mojom::VRDisplayInfoPtr GvrDevice::GetVRDevice() { |
| right_eye->offset[1] = 0.0; |
| right_eye->offset[2] = 0.03; |
| +#if 0 |
| // Tell the delegate not to draw yet, to avoid a race condition |
| // (and visible wobble) on entering VR. |
| if (delegate_) { |
| delegate_->SetWebVRRenderSurfaceSize(kInvalidRenderTargetSize.width, |
| kInvalidRenderTargetSize.height); |
| } |
| - |
| +#endif |
| return device; |
| } |
| - // In compositor mode, we have to use the current compositor window's |
| - // surface size. Would be nice to change it, but that needs more browser |
| - // internals to be modified. TODO(klausw,crbug.com/655722): remove this once |
| - // we can pick our own surface size. |
| - gvr::Sizei compositor_size = delegate_->GetWebVRCompositorSurfaceSize(); |
| - left_eye->renderWidth = compositor_size.width / 2; |
| - left_eye->renderHeight = compositor_size.height; |
| + render_target_size = gvr_api->GetMaximumEffectiveRenderTargetSize(); |
| + // Render at less than the maximum effective render target size as a |
| + // compromise between image quality and performance, similar to |
| + // current polyfill. TODO(klausw): allow clients to override this to |
| + // get full resolution. |
| + left_eye->renderWidth = render_target_size.width * 55 / 100 / 2; |
| + left_eye->renderHeight = render_target_size.height * 55 / 100; |
| right_eye->renderWidth = left_eye->renderWidth; |
| right_eye->renderHeight = left_eye->renderHeight; |
| + LOG(INFO) << "klausw: render_target_size=" << render_target_size.width << "x" |
| + << render_target_size.height; |
| std::string vendor = gvr_api->GetViewerVendor(); |
| std::string model = gvr_api->GetViewerModel(); |
| device->displayName = vendor + " " + model; |
| + VLOG(1) << __FUNCTION__ << ": gvr_api creating buffer viewports"; |
| gvr::BufferViewportList gvr_buffer_viewports = |
| gvr_api->CreateEmptyBufferViewportList(); |
| gvr_buffer_viewports.SetToRecommendedBufferViewports(); |
| @@ -142,24 +199,24 @@ mojom::VRDisplayInfoPtr GvrDevice::GetVRDevice() { |
| right_eye->offset[1] = -right_eye_mat.m[1][3]; |
| right_eye->offset[2] = -right_eye_mat.m[2][3]; |
| - if (delegate_) { |
| - delegate_->SetWebVRRenderSurfaceSize(2 * left_eye->renderWidth, |
| - left_eye->renderHeight); |
| - } |
| - |
| return device; |
| } |
| mojom::VRPosePtr GvrDevice::GetPose() { |
| TRACE_EVENT0("input", "GvrDevice::GetSensorState"); |
| - mojom::VRPosePtr pose = mojom::VRPose::New(); |
| - |
| - pose->timestamp = base::Time::Now().ToJsTime(); |
| - |
| // Increment pose frame counter always, even if it's a faked pose. |
| - pose->poseIndex = ++pose_index_; |
| + ++pose_index_; |
| + // Don't allow an actual pose index to be negative or zero, those |
| + // are reserved for error or "no pose" states. |
| + if (pose_index_ <= 0) pose_index_ = 1; |
| + VLOG(2) << __FUNCTION__ << ": frame " << pose_index_; |
| + TRACE_EVENT1("media", "klausw:GetPose", "frame", pose_index_); |
| + |
| + mojom::VRPosePtr pose = mojom::VRPose::New(); |
| + pose->timestamp = base::Time::Now().ToJsTime(); |
| + pose->poseIndex = pose_index_; |
| pose->orientation.emplace(4); |
| gvr::GvrApi* gvr_api = GetGvrApi(); |
| @@ -173,11 +230,26 @@ mojom::VRPosePtr GvrDevice::GetPose() { |
| return pose; |
| } |
| - if (!delegate_) |
| + if (!delegate_) { |
| + VLOG(2) << __FUNCTION__ << ": no delegate, no pose!"; |
| return nullptr; |
| + } |
| gvr::ClockTimePoint target_time = gvr::GvrApi::GetTimePointNow(); |
| - target_time.monotonic_system_time_nanos += kPredictionTimeWithoutVsyncNanos; |
| + int64_t pose_time = target_time.monotonic_system_time_nanos; |
| + |
| +#if 0 |
| + int64_t nstime = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count(); |
| + LOG(INFO) << "klausw: gvr ns=" << pose_time << " steady ns=" << steady_nanos << " delta=" << pose_time - nstime; |
| +#endif |
| + |
| + // TODO(klausw): sanity check |
| + double render_time_ms = last_processing_ms_.hasPrediction() && last_render_ms_.hasPrediction() ? |
| + last_processing_ms_.getPrediction() + last_render_ms_.getPrediction() : |
| + kDefaultRenderTimeMillis; |
| + double predict_time_ms = render_time_ms + kPredictionTimeWithoutVsyncMillis; |
| + VLOG(2) << __FUNCTION__ << ": predict_time_ms=" << predict_time_ms; |
| + target_time.monotonic_system_time_nanos += static_cast<uint64_t>(predict_time_ms * 1e6); |
| gvr::Mat4f head_mat = |
| gvr_api->GetHeadSpaceFromStartSpaceRotation(target_time); |
| @@ -207,7 +279,7 @@ mojom::VRPosePtr GvrDevice::GetPose() { |
| // Save the underlying GVR pose for use by rendering. It can't use a |
| // VRPosePtr since that's a different data type. |
| - delegate_->SetGvrPoseForWebVr(head_mat, pose_index_); |
| + delegate_->SetWebVRGvrPose(head_mat, pose_index_, pose_time); |
| return pose; |
| } |
| @@ -232,13 +304,50 @@ void GvrDevice::SetSecureOrigin(bool secure_origin) { |
| } |
| void GvrDevice::ExitPresent() { |
| + // Run pending "frame complete" callbacks |
| + for (auto it : callback_map_) { |
|
artem.bolgar
2017/02/14 05:04:24
This cleanup code block should be moved to GvrDevi
|
| + VLOG(2) << __FUNCTION__ << ": ExitPresent running pending callback for frame: " << it.first; |
| + it.second.Run(0, it.first, -1.0); |
| + } |
| + callback_map_.clear(); |
| gvr_provider_->ExitPresent(); |
| OnExitPresent(); |
| } |
| -void GvrDevice::SubmitFrame(mojom::VRPosePtr pose) { |
| - if (delegate_) |
| - delegate_->SubmitWebVRFrame(); |
| +void GvrDevice::SubmitFrame(int32_t surfaceHandle, |
| + mojom::VRPosePtr pose, |
| + const mojom::VRDisplay::SubmitFrameCallback& callback) { |
| + TRACE_EVENT1("media", "klausw:SubmitFrame", "frame", pose->poseIndex); |
| + VLOG(2) << __FUNCTION__ << ": frame " << pose->poseIndex; |
| + if (delegate_) { |
| + VLOG(2) << __FUNCTION__ << ": save callback for frame index " << pose->poseIndex; |
| + callback_map_.insert(std::make_pair(pose->poseIndex, std::move(callback))); |
| + last_processing_ms_.add(pose->ts_submit - pose->ts_getPose); |
| + delegate_->SubmitWebVRFrame(surfaceHandle, std::move(pose)); |
| + } else { |
| + VLOG(2) << __FUNCTION__ << ": No delegate, calling callback now for frame index " << pose->poseIndex; |
| + callback.Run(surfaceHandle, pose->poseIndex, -1.0); |
| + VLOG(2) << __FUNCTION__ << ": run callback done"; |
| + } |
| +} |
| + |
| +void GvrDevice::OnFrameSubmitted(int32_t surfaceHandle, uint32_t frame_index, double elapsed) { |
| + auto it = callback_map_.find(frame_index); |
| + if (it == callback_map_.end()) { |
| + VLOG(2) << __FUNCTION__ << ": no callback found for frame index " << frame_index; |
| + } else { |
| + VLOG(2) << __FUNCTION__ << ": run callback for frame index " << frame_index; |
| + auto callback = std::move(it->second); |
| + callback.Run(surfaceHandle, frame_index, elapsed); |
| + last_render_ms_.add(elapsed); |
| + VLOG(2) << __FUNCTION__ << ": run callback done, erasing"; |
| + callback_map_.erase(it); |
| + VLOG(2) << __FUNCTION__ << ": erasing done"; |
| + } |
| + |
| + for (auto it : callback_map_) { |
| + VLOG(2) << __FUNCTION__ << ": pending frame: " << it.first; |
| + } |
| } |
| void GvrDevice::UpdateLayerBounds(mojom::VRLayerBoundsPtr left_bounds, |
| @@ -258,7 +367,16 @@ void GvrDevice::UpdateLayerBounds(mojom::VRLayerBoundsPtr left_bounds, |
| right_gvr_bounds.right = right_bounds->left + right_bounds->width; |
| right_gvr_bounds.bottom = 1.0f - (right_bounds->top + right_bounds->height); |
| - delegate_->UpdateWebVRTextureBounds(left_gvr_bounds, right_gvr_bounds); |
| + // TODO(klausw): tie this to pose_index_ for future execution |
| + delegate_->UpdateWebVRTextureBounds(left_bounds->forPoseIndex, left_gvr_bounds, right_gvr_bounds); |
| +} |
| + |
| +void GvrDevice::GetSurfaceHandle(int32_t width, int32_t height, const mojom::VRDisplay::GetSurfaceHandleCallback& callback) { |
| + // LOG(ERROR) << "klausw:GvrDevice.GetSurfaceHandle, delegate=" << delegate_; |
| + if (!delegate_) |
| + callback.Run(0); |
| + |
| + delegate_->GetWebVRSurfaceHandle(width, height, callback); |
| } |
| void GvrDevice::SetDelegate(GvrDelegate* delegate) { |