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

Side by Side Diff: ash/laser/laser_pointer_view.cc

Issue 2745953002: ash: Add basic prediction code to the laser pointer. (Closed)
Patch Set: more tests and nits 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
« no previous file with comments | « ash/laser/laser_pointer_view.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 "ash/laser/laser_pointer_view.h" 5 #include "ash/laser/laser_pointer_view.h"
6 6
7 #include <GLES2/gl2.h> 7 #include <GLES2/gl2.h>
8 #include <GLES2/gl2ext.h> 8 #include <GLES2/gl2ext.h>
9 #include <GLES2/gl2extchromium.h> 9 #include <GLES2/gl2extchromium.h>
10 10
11 #include <algorithm>
12 #include <array>
13 #include <cmath>
11 #include <memory> 14 #include <memory>
12 15
13 #include "ash/laser/laser_pointer_points.h" 16 #include "ash/laser/laser_pointer_points.h"
14 #include "ash/laser/laser_segment_utils.h" 17 #include "ash/laser/laser_segment_utils.h"
15 #include "ash/public/cpp/shell_window_ids.h" 18 #include "ash/public/cpp/shell_window_ids.h"
16 #include "ash/shell.h" 19 #include "ash/shell.h"
20 #include "base/containers/adapters.h"
17 #include "base/threading/thread_task_runner_handle.h" 21 #include "base/threading/thread_task_runner_handle.h"
18 #include "base/timer/timer.h" 22 #include "base/timer/timer.h"
19 #include "base/trace_event/trace_event.h" 23 #include "base/trace_event/trace_event.h"
20 #include "cc/output/context_provider.h" 24 #include "cc/output/context_provider.h"
21 #include "cc/quads/texture_draw_quad.h" 25 #include "cc/quads/texture_draw_quad.h"
22 #include "cc/resources/transferable_resource.h" 26 #include "cc/resources/transferable_resource.h"
23 #include "cc/surfaces/surface.h" 27 #include "cc/surfaces/surface.h"
24 #include "cc/surfaces/surface_manager.h" 28 #include "cc/surfaces/surface_manager.h"
25 #include "gpu/command_buffer/client/context_support.h" 29 #include "gpu/command_buffer/client/context_support.h"
26 #include "gpu/command_buffer/client/gles2_interface.h" 30 #include "gpu/command_buffer/client/gles2_interface.h"
27 #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h" 31 #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h"
28 #include "third_party/skia/include/core/SkColor.h" 32 #include "third_party/skia/include/core/SkColor.h"
29 #include "third_party/skia/include/core/SkTypes.h" 33 #include "third_party/skia/include/core/SkTypes.h"
30 #include "ui/aura/env.h" 34 #include "ui/aura/env.h"
31 #include "ui/aura/window.h" 35 #include "ui/aura/window.h"
32 #include "ui/display/display.h" 36 #include "ui/display/display.h"
33 #include "ui/display/screen.h" 37 #include "ui/display/screen.h"
38 #include "ui/events/base_event_utils.h"
34 #include "ui/events/event.h" 39 #include "ui/events/event.h"
35 #include "ui/gfx/canvas.h" 40 #include "ui/gfx/canvas.h"
36 #include "ui/gfx/gpu_memory_buffer.h" 41 #include "ui/gfx/gpu_memory_buffer.h"
37 #include "ui/views/widget/widget.h" 42 #include "ui/views/widget/widget.h"
38 43
39 namespace ash { 44 namespace ash {
40 namespace { 45 namespace {
41 46
42 // Variables for rendering the laser. Radius in DIP. 47 // Variables for rendering the laser. Radius in DIP.
43 const float kPointInitialRadius = 5.0f; 48 const float kPointInitialRadius = 5.0f;
44 const float kPointFinalRadius = 0.25f; 49 const float kPointFinalRadius = 0.25f;
45 const int kPointInitialOpacity = 200; 50 const int kPointInitialOpacity = 200;
46 const int kPointFinalOpacity = 10; 51 const int kPointFinalOpacity = 10;
47 const SkColor kPointColor = SkColorSetRGB(255, 0, 0); 52 const SkColor kPointColor = SkColorSetRGB(255, 0, 0);
53 // Change this when debugging prediction code.
54 const SkColor kPredictionPointColor = kPointColor;
48 55
49 float DistanceBetweenPoints(const gfx::PointF& point1, 56 float DistanceBetweenPoints(const gfx::PointF& point1,
50 const gfx::PointF& point2) { 57 const gfx::PointF& point2) {
51 return (point1 - point2).Length(); 58 return (point1 - point2).Length();
52 } 59 }
53 60
54 float LinearInterpolate(float initial_value, 61 float LinearInterpolate(float initial_value,
55 float final_value, 62 float final_value,
56 float progress) { 63 float progress) {
57 return initial_value + (final_value - initial_value) * progress; 64 return initial_value + (final_value - initial_value) * progress;
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after
189 } 196 }
190 } 197 }
191 scoped_refptr<cc::ContextProvider> context_provider; 198 scoped_refptr<cc::ContextProvider> context_provider;
192 uint32_t texture = 0; 199 uint32_t texture = 0;
193 uint32_t image = 0; 200 uint32_t image = 0;
194 gpu::Mailbox mailbox; 201 gpu::Mailbox mailbox;
195 }; 202 };
196 203
197 // LaserPointerView 204 // LaserPointerView
198 LaserPointerView::LaserPointerView(base::TimeDelta life_duration, 205 LaserPointerView::LaserPointerView(base::TimeDelta life_duration,
206 base::TimeDelta presentation_delay,
199 aura::Window* root_window) 207 aura::Window* root_window)
200 : laser_points_(life_duration), 208 : laser_points_(life_duration),
209 predicted_laser_points_(life_duration),
210 presentation_delay_(presentation_delay),
201 frame_sink_id_(aura::Env::GetInstance() 211 frame_sink_id_(aura::Env::GetInstance()
202 ->context_factory_private() 212 ->context_factory_private()
203 ->AllocateFrameSinkId()), 213 ->AllocateFrameSinkId()),
204 frame_sink_support_(this, 214 frame_sink_support_(this,
205 aura::Env::GetInstance() 215 aura::Env::GetInstance()
206 ->context_factory_private() 216 ->context_factory_private()
207 ->GetSurfaceManager(), 217 ->GetSurfaceManager(),
208 frame_sink_id_, 218 frame_sink_id_,
209 false /* is_root */, 219 false /* is_root */,
210 true /* handles_frame_sink_id_invalidation */, 220 true /* handles_frame_sink_id_invalidation */,
(...skipping 24 matching lines...) Expand all
235 245
236 LaserPointerView::~LaserPointerView() { 246 LaserPointerView::~LaserPointerView() {
237 // Make sure GPU memory buffer is unmapped before being destroyed. 247 // Make sure GPU memory buffer is unmapped before being destroyed.
238 if (gpu_memory_buffer_) 248 if (gpu_memory_buffer_)
239 gpu_memory_buffer_->Unmap(); 249 gpu_memory_buffer_->Unmap();
240 } 250 }
241 251
242 void LaserPointerView::Stop() { 252 void LaserPointerView::Stop() {
243 buffer_damage_rect_.Union(GetBoundingBox()); 253 buffer_damage_rect_.Union(GetBoundingBox());
244 laser_points_.Clear(); 254 laser_points_.Clear();
255 predicted_laser_points_.Clear();
245 OnPointsUpdated(); 256 OnPointsUpdated();
246 } 257 }
247 258
248 void LaserPointerView::AddNewPoint(const gfx::PointF& new_point) { 259 void LaserPointerView::AddNewPoint(const gfx::PointF& new_point,
260 const base::TimeTicks& new_time) {
261 TRACE_EVENT1("ui", "LaserPointerView::AddNewPoint", "new_point",
262 new_point.ToString());
263 TRACE_COUNTER1(
264 "ui", "LaserPointerPredictionError",
265 predicted_laser_points_.GetNumberOfPoints()
266 ? std::round((new_point -
267 predicted_laser_points_.laser_points().front().location)
268 .Length())
269 : 0);
270
249 buffer_damage_rect_.Union(GetBoundingBox()); 271 buffer_damage_rect_.Union(GetBoundingBox());
250 laser_points_.AddPoint(new_point); 272 laser_points_.AddPoint(new_point, new_time);
273
274 // Current time is needed to determine presentation time and the number of
275 // predicted points to add.
276 base::TimeTicks current_time = ui::EventTimeForNow();
277
278 // Create a new set of predicted points based on the last four points added.
279 // We add enough predicted points to fill the time between the new point and
280 // the expected presentation time. Note that estimated presentation time is
281 // based on current time and inefficient rendering of points can result in an
282 // actual presentation time that is later.
283 predicted_laser_points_.Clear();
284
285 // Normalize all coordinates to screen size.
286 gfx::Size screen_size = widget_->GetNativeView()->GetBoundsInScreen().size();
287 gfx::Vector2dF scale(1.0f / screen_size.width(), 1.0f / screen_size.height());
288
289 // TODO(reveman): Determine interval based on history when event time stamps
290 // are accurate. b/36137953
291 const float kPredictionIntervalMs = 5.0f;
292 const float kMaxPointIntervalMs = 10.0f;
293 base::TimeDelta prediction_interval =
294 base::TimeDelta::FromMilliseconds(kPredictionIntervalMs);
295 base::TimeDelta max_point_interval =
296 base::TimeDelta::FromMilliseconds(kMaxPointIntervalMs);
297 base::TimeTicks last_point_time = current_time;
298
299 // Use the last four points for prediction.
300 using PositionArray = std::array<gfx::PointF, 4>;
301 PositionArray position;
302 PositionArray::iterator it = position.begin();
303 for (const auto& point : base::Reversed(laser_points_.laser_points())) {
304 // Stop adding positions if interval between points is too large to provide
305 // an accurate history for prediction.
306 if ((last_point_time - point.time) > max_point_interval)
307 break;
308
309 *it++ = gfx::ScalePoint(point.location, scale.x(), scale.y());
310 last_point_time = point.time;
311
312 // Stop when no more positions are needed.
313 if (it == position.end())
314 break;
315 }
316 // Pad with last point if needed.
317 std::fill(it, position.end(), *(it - 1));
318
319 // Note: Currently there's no need to divide by the time delta between
320 // points as we assume a constant delta between points that matches the
321 // prediction point interval.
322 gfx::Vector2dF velocity[3];
323 for (size_t i = 0; i < arraysize(velocity); ++i)
324 velocity[i] = position[i] - position[i + 1];
325
326 gfx::Vector2dF acceleration[2];
327 for (size_t i = 0; i < arraysize(acceleration); ++i)
328 acceleration[i] = velocity[i] - velocity[i + 1];
329
330 gfx::Vector2dF jerk = acceleration[0] - acceleration[1];
331
332 // Adjust max prediction time based on speed as prediction data is not great
333 // at lower speeds.
334 const float kMaxPredictionScaleSpeed = 1e-5;
335 double speed = velocity[0].LengthSquared();
336 base::TimeTicks max_prediction_time =
337 current_time +
338 std::min(presentation_delay_ * (speed / kMaxPredictionScaleSpeed),
339 presentation_delay_);
340
341 // Add predicted points until we reach the max prediction time.
342 gfx::PointF location = position[0];
343 for (base::TimeTicks time = new_time + prediction_interval;
344 time < max_prediction_time; time += prediction_interval) {
345 // Note: Currently there's no need to multiply by the prediction interval
346 // as the velocity is calculated based on a time delta between points that
347 // is the same as the prediction interval.
348 velocity[0] += acceleration[0];
349 acceleration[0] += jerk;
350 location += velocity[0];
351
352 predicted_laser_points_.AddPoint(
353 gfx::ScalePoint(location, screen_size.width(), screen_size.height()),
354 time);
355
356 // Always stop at three predicted points as a four point history doesn't
357 // provide accurate prediction of more points.
358 if (predicted_laser_points_.GetNumberOfPoints() == 3)
359 break;
360 }
361
362 // Move forward to next presentation time.
363 base::TimeTicks next_presentation_time = current_time + presentation_delay_;
364 laser_points_.MoveForwardToTime(next_presentation_time);
365 predicted_laser_points_.MoveForwardToTime(next_presentation_time);
366
251 buffer_damage_rect_.Union(GetBoundingBox()); 367 buffer_damage_rect_.Union(GetBoundingBox());
252 OnPointsUpdated(); 368 OnPointsUpdated();
253 } 369 }
254 370
255 void LaserPointerView::UpdateTime() { 371 void LaserPointerView::UpdateTime() {
256 buffer_damage_rect_.Union(GetBoundingBox()); 372 buffer_damage_rect_.Union(GetBoundingBox());
257 // Do not add the point but advance the time if the view is in process of 373 // Do not add the point but advance the time if the view is in process of
258 // fading away. 374 // fading away.
259 laser_points_.MoveForwardToTime(base::Time::Now()); 375 base::TimeTicks next_presentation_time =
376 ui::EventTimeForNow() + presentation_delay_;
377 laser_points_.MoveForwardToTime(next_presentation_time);
378 predicted_laser_points_.MoveForwardToTime(next_presentation_time);
260 buffer_damage_rect_.Union(GetBoundingBox()); 379 buffer_damage_rect_.Union(GetBoundingBox());
261 OnPointsUpdated(); 380 OnPointsUpdated();
262 } 381 }
263 382
264 void LaserPointerView::SetNeedsBeginFrame(bool needs_begin_frame) { 383 void LaserPointerView::SetNeedsBeginFrame(bool needs_begin_frame) {
265 frame_sink_support_.SetNeedsBeginFrame(needs_begin_frame); 384 frame_sink_support_.SetNeedsBeginFrame(needs_begin_frame);
266 } 385 }
267 386
268 void LaserPointerView::SubmitCompositorFrame( 387 void LaserPointerView::SubmitCompositorFrame(
269 const cc::LocalSurfaceId& local_surface_id, 388 const cc::LocalSurfaceId& local_surface_id,
(...skipping 25 matching lines...) Expand all
295 gles2->WaitSyncTokenCHROMIUM(resources.front().sync_token.GetConstData()); 414 gles2->WaitSyncTokenCHROMIUM(resources.front().sync_token.GetConstData());
296 415
297 if (!resources.front().lost) 416 if (!resources.front().lost)
298 returned_resources_.push_back(std::move(resource)); 417 returned_resources_.push_back(std::move(resource));
299 } 418 }
300 419
301 gfx::Rect LaserPointerView::GetBoundingBox() { 420 gfx::Rect LaserPointerView::GetBoundingBox() {
302 // Expand the bounding box so that it includes the radius of the points on the 421 // Expand the bounding box so that it includes the radius of the points on the
303 // edges and antialiasing. 422 // edges and antialiasing.
304 gfx::Rect bounding_box = laser_points_.GetBoundingBox(); 423 gfx::Rect bounding_box = laser_points_.GetBoundingBox();
424 bounding_box.Union(predicted_laser_points_.GetBoundingBox());
305 const int kOutsetForAntialiasing = 1; 425 const int kOutsetForAntialiasing = 1;
306 int outset = kPointInitialRadius + kOutsetForAntialiasing; 426 int outset = kPointInitialRadius + kOutsetForAntialiasing;
307 bounding_box.Inset(-outset, -outset); 427 bounding_box.Inset(-outset, -outset);
308 return bounding_box; 428 return bounding_box;
309 } 429 }
310 430
311 void LaserPointerView::OnPointsUpdated() { 431 void LaserPointerView::OnPointsUpdated() {
312 if (pending_update_buffer_) 432 if (pending_update_buffer_)
313 return; 433 return;
314 434
315 pending_update_buffer_ = true; 435 pending_update_buffer_ = true;
316 base::ThreadTaskRunnerHandle::Get()->PostTask( 436 base::ThreadTaskRunnerHandle::Get()->PostTask(
317 FROM_HERE, base::Bind(&LaserPointerView::UpdateBuffer, 437 FROM_HERE, base::Bind(&LaserPointerView::UpdateBuffer,
318 weak_ptr_factory_.GetWeakPtr())); 438 weak_ptr_factory_.GetWeakPtr()));
319 } 439 }
320 440
321 void LaserPointerView::UpdateBuffer() { 441 void LaserPointerView::UpdateBuffer() {
322 TRACE_EVENT2("ui", "LaserPointerView::UpdatedBuffer", "damage", 442 TRACE_EVENT1("ui", "LaserPointerView::UpdatedBuffer", "damage",
323 buffer_damage_rect_.ToString(), "points", 443 buffer_damage_rect_.ToString());
324 laser_points_.GetNumberOfPoints());
325 444
326 DCHECK(pending_update_buffer_); 445 DCHECK(pending_update_buffer_);
327 pending_update_buffer_ = false; 446 pending_update_buffer_ = false;
328 447
329 gfx::Rect screen_bounds = widget_->GetNativeView()->GetBoundsInScreen(); 448 gfx::Rect screen_bounds = widget_->GetNativeView()->GetBoundsInScreen();
330 gfx::Rect update_rect = buffer_damage_rect_; 449 gfx::Rect update_rect = buffer_damage_rect_;
331 buffer_damage_rect_ = gfx::Rect(); 450 buffer_damage_rect_ = gfx::Rect();
332 451
333 // Create and map a single GPU memory buffer. The laser pointer will be 452 // Create and map a single GPU memory buffer. The laser pointer will be
334 // written into this buffer without any buffering. The result is that we 453 // written into this buffer without any buffering. The result is that we
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
372 491
373 cc::PaintFlags flags; 492 cc::PaintFlags flags;
374 flags.setStyle(cc::PaintFlags::kFill_Style); 493 flags.setStyle(cc::PaintFlags::kFill_Style);
375 flags.setAntiAlias(true); 494 flags.setAntiAlias(true);
376 495
377 // Compute the offset of the current widget. 496 // Compute the offset of the current widget.
378 gfx::Vector2d widget_offset( 497 gfx::Vector2d widget_offset(
379 widget_->GetNativeView()->GetBoundsInRootWindow().origin().x(), 498 widget_->GetNativeView()->GetBoundsInRootWindow().origin().x(),
380 widget_->GetNativeView()->GetBoundsInRootWindow().origin().y()); 499 widget_->GetNativeView()->GetBoundsInRootWindow().origin().y());
381 500
382 int num_points = laser_points_.GetNumberOfPoints(); 501 int num_points = laser_points_.GetNumberOfPoints() +
502 predicted_laser_points_.GetNumberOfPoints();
383 if (num_points) { 503 if (num_points) {
384 LaserPointerPoints::LaserPoint previous_point = laser_points_.GetOldest(); 504 LaserPointerPoints::LaserPoint previous_point = laser_points_.GetOldest();
385 previous_point.location -= widget_offset + update_rect.OffsetFromOrigin(); 505 previous_point.location -= widget_offset + update_rect.OffsetFromOrigin();
386 LaserPointerPoints::LaserPoint current_point; 506 LaserPointerPoints::LaserPoint current_point;
387 std::vector<gfx::PointF> previous_segment_points; 507 std::vector<gfx::PointF> previous_segment_points;
388 float previous_radius; 508 float previous_radius;
389 int current_opacity; 509 int current_opacity;
390 510
391 for (int i = 0; i < num_points; ++i) { 511 for (int i = 0; i < num_points; ++i) {
392 current_point = laser_points_.laser_points()[i]; 512 if (i < laser_points_.GetNumberOfPoints()) {
513 current_point = laser_points_.laser_points()[i];
514 } else {
515 current_point =
516 predicted_laser_points_
517 .laser_points()[i - laser_points_.GetNumberOfPoints()];
518 }
393 current_point.location -= widget_offset + update_rect.OffsetFromOrigin(); 519 current_point.location -= widget_offset + update_rect.OffsetFromOrigin();
394 520
395 // Set the radius and opacity based on the distance. 521 // Set the radius and opacity based on the distance.
396 float current_radius = LinearInterpolate( 522 float current_radius = LinearInterpolate(
397 kPointInitialRadius, kPointFinalRadius, current_point.age); 523 kPointInitialRadius, kPointFinalRadius, current_point.age);
398 current_opacity = int{LinearInterpolate( 524 current_opacity = int{LinearInterpolate(
399 kPointInitialOpacity, kPointFinalOpacity, current_point.age)}; 525 kPointInitialOpacity, kPointFinalOpacity, current_point.age)};
400 526
401 // If we draw laser_points_ that are within a stroke width of each other, 527 // If we draw laser_points_ that are within a stroke width of each other,
402 // the result will be very jagged, unless we are on the last point, then 528 // the result will be very jagged, unless we are on the last point, then
403 // we draw regardless. 529 // we draw regardless.
404 float distance_threshold = current_radius * 2.0f; 530 float distance_threshold = current_radius * 2.0f;
405 if (DistanceBetweenPoints(previous_point.location, 531 if (DistanceBetweenPoints(previous_point.location,
406 current_point.location) <= distance_threshold && 532 current_point.location) <= distance_threshold &&
407 i != num_points - 1) { 533 i != num_points - 1) {
408 continue; 534 continue;
409 } 535 }
410 536
411 LaserSegment current_segment( 537 LaserSegment current_segment(
412 previous_segment_points, gfx::PointF(previous_point.location), 538 previous_segment_points, gfx::PointF(previous_point.location),
413 gfx::PointF(current_point.location), previous_radius, current_radius, 539 gfx::PointF(current_point.location), previous_radius, current_radius,
414 i == num_points - 1); 540 i == num_points - 1);
415 541
416 SkPath path = current_segment.path(); 542 SkPath path = current_segment.path();
417 flags.setColor(SkColorSetA(kPointColor, current_opacity)); 543 if (i < laser_points_.GetNumberOfPoints())
544 flags.setColor(SkColorSetA(kPointColor, current_opacity));
545 else
546 flags.setColor(SkColorSetA(kPredictionPointColor, current_opacity));
418 canvas.DrawPath(path, flags); 547 canvas.DrawPath(path, flags);
419 548
420 previous_segment_points = current_segment.path_points(); 549 previous_segment_points = current_segment.path_points();
421 previous_radius = current_radius; 550 previous_radius = current_radius;
422 previous_point = current_point; 551 previous_point = current_point;
423 } 552 }
424 553
425 // Draw the last point as a circle. 554 // Draw the last point as a circle.
426 flags.setColor(SkColorSetA(kPointColor, current_opacity));
427 flags.setStyle(cc::PaintFlags::kFill_Style); 555 flags.setStyle(cc::PaintFlags::kFill_Style);
428 canvas.DrawCircle(current_point.location, kPointInitialRadius, flags); 556 canvas.DrawCircle(current_point.location, kPointInitialRadius, flags);
429 } 557 }
430 558
431 // Copy result to GPU memory buffer. This is effectiely a memcpy and unlike 559 // Copy result to GPU memory buffer. This is effectiely a memcpy and unlike
432 // drawing to the buffer directly this ensures that the buffer is never in a 560 // drawing to the buffer directly this ensures that the buffer is never in a
433 // state that would result in flicker. 561 // state that would result in flicker.
434 { 562 {
435 TRACE_EVENT0("ui", "LaserPointerView::OnPointsUpdated::Copy"); 563 TRACE_EVENT0("ui", "LaserPointerView::OnPointsUpdated::Copy");
436 564
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after
584 pending_draw_surface_ = true; 712 pending_draw_surface_ = true;
585 } 713 }
586 714
587 void LaserPointerView::OnDidDrawSurface() { 715 void LaserPointerView::OnDidDrawSurface() {
588 pending_draw_surface_ = false; 716 pending_draw_surface_ = false;
589 if (needs_update_surface_) 717 if (needs_update_surface_)
590 UpdateSurface(); 718 UpdateSurface();
591 } 719 }
592 720
593 } // namespace ash 721 } // namespace ash
OLDNEW
« no previous file with comments | « ash/laser/laser_pointer_view.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698