OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 "remoting/client/plugin/pepper_view.h" | 5 #include "remoting/client/plugin/pepper_view.h" |
6 | 6 |
7 #include "base/message_loop.h" | 7 #include "base/message_loop.h" |
8 #include "base/string_util.h" | 8 #include "base/string_util.h" |
9 #include "ppapi/cpp/completion_callback.h" | 9 #include "ppapi/cpp/completion_callback.h" |
10 #include "ppapi/cpp/graphics_2d.h" | 10 #include "ppapi/cpp/graphics_2d.h" |
(...skipping 28 matching lines...) Expand all Loading... |
39 DLOG(FATAL) << "Unknown error code" << error; | 39 DLOG(FATAL) << "Unknown error code" << error; |
40 return ChromotingScriptableObject::ERROR_NONE; | 40 return ChromotingScriptableObject::ERROR_NONE; |
41 } | 41 } |
42 | 42 |
43 } // namespace | 43 } // namespace |
44 | 44 |
45 PepperView::PepperView(ChromotingInstance* instance, ClientContext* context) | 45 PepperView::PepperView(ChromotingInstance* instance, ClientContext* context) |
46 : instance_(instance), | 46 : instance_(instance), |
47 context_(context), | 47 context_(context), |
48 flush_blocked_(false), | 48 flush_blocked_(false), |
| 49 view_changed_(false), |
| 50 view_size_(SkISize::Make(0, 0)), |
| 51 clip_area_(SkIRect::MakeEmpty()), |
| 52 screen_size_(SkISize::Make(0, 0)), |
49 is_static_fill_(false), | 53 is_static_fill_(false), |
50 static_fill_color_(0), | 54 static_fill_color_(0), |
51 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { | 55 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { |
52 } | 56 } |
53 | 57 |
54 PepperView::~PepperView() { | 58 PepperView::~PepperView() { |
55 } | 59 } |
56 | 60 |
57 bool PepperView::Initialize() { | 61 bool PepperView::Initialize() { |
58 return true; | 62 return true; |
59 } | 63 } |
60 | 64 |
61 void PepperView::TearDown() { | 65 void PepperView::TearDown() { |
62 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); | 66 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); |
63 | 67 |
64 weak_factory_.InvalidateWeakPtrs(); | 68 weak_factory_.InvalidateWeakPtrs(); |
65 } | 69 } |
66 | 70 |
67 void PepperView::Paint() { | 71 void PepperView::Paint() { |
68 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); | 72 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); |
69 | 73 |
70 if (is_static_fill_) { | 74 if (is_static_fill_ && !clip_area_.isEmpty()) { |
71 VLOG(1) << "Static filling " << static_fill_color_; | 75 VLOG(1) << "Static filling " << static_fill_color_; |
72 pp::ImageData image(instance_, pp::ImageData::GetNativeImageDataFormat(), | 76 pp::ImageData image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL, |
73 pp::Size(graphics2d_.size().width(), | 77 pp::Size(clip_area_.width(), |
74 graphics2d_.size().height()), | 78 clip_area_.height()), |
75 false); | 79 false); |
76 if (image.is_null()) { | 80 if (image.is_null()) { |
77 LOG(ERROR) << "Unable to allocate image of size: " | 81 LOG(ERROR) << "Unable to allocate image of size: " |
78 << graphics2d_.size().width() << " x " | 82 << clip_area_.width() << " x " |
79 << graphics2d_.size().height(); | 83 << clip_area_.height(); |
80 return; | 84 return; |
81 } | 85 } |
82 | 86 |
83 for (int y = 0; y < image.size().height(); y++) { | 87 for (int y = 0; y < image.size().height(); y++) { |
84 for (int x = 0; x < image.size().width(); x++) { | 88 for (int x = 0; x < image.size().width(); x++) { |
85 *image.GetAddr32(pp::Point(x, y)) = static_fill_color_; | 89 *image.GetAddr32(pp::Point(x, y)) = static_fill_color_; |
86 } | 90 } |
87 } | 91 } |
88 | 92 |
89 // For ReplaceContents, make sure the image size matches the device context | 93 graphics2d_.PaintImageData( |
90 // size! Otherwise, this will just silently do nothing. | 94 image, |
91 graphics2d_.ReplaceContents(&image); | 95 pp::Point(clip_area_.left(), clip_area_.top())); |
92 FlushGraphics(base::Time::Now()); | 96 FlushGraphics(base::Time::Now()); |
93 } else { | 97 } else { |
94 // TODO(ajwong): We need to keep a backing store image of the host screen | 98 // TODO(ajwong): We need to keep a backing store image of the host screen |
95 // that has the data here which can be redrawn. | 99 // that has the data here which can be redrawn. |
96 return; | 100 return; |
97 } | 101 } |
98 } | 102 } |
99 | 103 |
100 void PepperView::SetHostSize(const SkISize& host_size) { | 104 void PepperView::SetScreenSize(const SkISize& screen_size) { |
101 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); | 105 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); |
102 | 106 |
103 if (host_size_ == host_size) | 107 if (screen_size_ == screen_size) |
104 return; | 108 return; |
105 | 109 |
106 host_size_ = host_size; | 110 screen_size_ = screen_size; |
107 | 111 |
108 // Submit an update of desktop size to Javascript. | 112 // Submit an update of desktop size to Javascript. |
109 instance_->GetScriptableObject()->SetDesktopSize( | 113 instance_->GetScriptableObject()->SetDesktopSize( |
110 host_size.width(), host_size.height()); | 114 screen_size.width(), screen_size.height()); |
111 } | |
112 | |
113 void PepperView::PaintFrame(media::VideoFrame* frame, const SkRegion& region) { | |
114 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); | |
115 | |
116 SetHostSize(SkISize::Make(frame->width(), frame->height())); | |
117 | |
118 if (!backing_store_.get() || backing_store_->is_null()) { | |
119 LOG(ERROR) << "Backing store is not available."; | |
120 return; | |
121 } | |
122 | |
123 base::Time start_time = base::Time::Now(); | |
124 | |
125 // Copy updated regions to the backing store and then paint the regions. | |
126 bool changes_made = false; | |
127 for (SkRegion::Iterator i(region); !i.done(); i.next()) | |
128 changes_made |= PaintRect(frame, i.rect()); | |
129 | |
130 if (changes_made) | |
131 FlushGraphics(start_time); | |
132 } | |
133 | |
134 bool PepperView::PaintRect(media::VideoFrame* frame, const SkIRect& r) { | |
135 const uint8* frame_data = frame->data(media::VideoFrame::kRGBPlane); | |
136 const int kFrameStride = frame->stride(media::VideoFrame::kRGBPlane); | |
137 const int kBytesPerPixel = GetBytesPerPixel(media::VideoFrame::RGB32); | |
138 | |
139 pp::Size backing_store_size = backing_store_->size(); | |
140 SkIRect rect(r); | |
141 if (!rect.intersect(SkIRect::MakeWH(backing_store_size.width(), | |
142 backing_store_size.height()))) { | |
143 return false; | |
144 } | |
145 | |
146 const uint8* in = | |
147 frame_data + | |
148 kFrameStride * rect.fTop + // Y offset. | |
149 kBytesPerPixel * rect.fLeft; // X offset. | |
150 uint8* out = | |
151 reinterpret_cast<uint8*>(backing_store_->data()) + | |
152 backing_store_->stride() * rect.fTop + // Y offset. | |
153 kBytesPerPixel * rect.fLeft; // X offset. | |
154 | |
155 // TODO(hclam): We really should eliminate this memory copy. | |
156 for (int j = 0; j < rect.height(); ++j) { | |
157 memcpy(out, in, rect.width() * kBytesPerPixel); | |
158 in += kFrameStride; | |
159 out += backing_store_->stride(); | |
160 } | |
161 | |
162 // Pepper Graphics 2D has a strange and badly documented API that the | |
163 // point here is the offset from the source rect. Why? | |
164 graphics2d_.PaintImageData( | |
165 *backing_store_.get(), | |
166 pp::Point(0, 0), | |
167 pp::Rect(rect.fLeft, rect.fTop, rect.width(), rect.height())); | |
168 return true; | |
169 } | |
170 | |
171 void PepperView::BlankRect(pp::ImageData& image_data, const pp::Rect& rect) { | |
172 const int kBytesPerPixel = GetBytesPerPixel(media::VideoFrame::RGB32); | |
173 for (int y = rect.y(); y < rect.bottom(); y++) { | |
174 uint8* to = reinterpret_cast<uint8*>(image_data.data()) + | |
175 (y * image_data.stride()) + (rect.x() * kBytesPerPixel); | |
176 memset(to, 0xff, rect.width() * kBytesPerPixel); | |
177 } | |
178 } | 115 } |
179 | 116 |
180 void PepperView::FlushGraphics(base::Time paint_start) { | 117 void PepperView::FlushGraphics(base::Time paint_start) { |
181 scoped_ptr<base::Closure> task( | 118 scoped_ptr<base::Closure> task( |
182 new base::Closure( | 119 new base::Closure( |
183 base::Bind(&PepperView::OnPaintDone, weak_factory_.GetWeakPtr(), | 120 base::Bind(&PepperView::OnFlushDone, weak_factory_.GetWeakPtr(), |
184 paint_start))); | 121 paint_start))); |
185 | 122 |
186 // Flag needs to be set here in order to get a proper error code for Flush(). | 123 // Flag needs to be set here in order to get a proper error code for Flush(). |
187 // Otherwise Flush() will always return PP_OK_COMPLETIONPENDING and the error | 124 // Otherwise Flush() will always return PP_OK_COMPLETIONPENDING and the error |
188 // would be hidden. | 125 // would be hidden. |
189 // | 126 // |
190 // Note that we can also handle this by providing an actual callback which | 127 // Note that we can also handle this by providing an actual callback which |
191 // takes the result code. Right now everything goes to the task that doesn't | 128 // takes the result code. Right now everything goes to the task that doesn't |
192 // result value. | 129 // result value. |
193 pp::CompletionCallback pp_callback(&CompletionCallbackClosureAdapter, | 130 pp::CompletionCallback pp_callback(&CompletionCallbackClosureAdapter, |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
255 | 192 |
256 case protocol::ConnectionToHost::FAILED: | 193 case protocol::ConnectionToHost::FAILED: |
257 SetSolidFill(kFailedColor); | 194 SetSolidFill(kFailedColor); |
258 scriptable_obj->SetConnectionStatus( | 195 scriptable_obj->SetConnectionStatus( |
259 ChromotingScriptableObject::STATUS_FAILED, | 196 ChromotingScriptableObject::STATUS_FAILED, |
260 ConvertConnectionError(error)); | 197 ConvertConnectionError(error)); |
261 break; | 198 break; |
262 } | 199 } |
263 } | 200 } |
264 | 201 |
265 bool PepperView::SetViewSize(const SkISize& view_size) { | 202 bool PepperView::SetView(const SkISize& view_size, |
266 if (view_size_ == view_size) | 203 const SkIRect& clip_area) { |
267 return false; | 204 // Make sure that the clip area is entirely contained within |
268 view_size_ = view_size; | 205 // the view area. |
| 206 DCHECK(!view_size.isEmpty() || clip_area.isEmpty()); |
| 207 DCHECK(view_size.isEmpty() || |
| 208 SkIRect::MakeSize(view_size).contains(clip_area)); |
269 | 209 |
270 pp::Size pp_size = pp::Size(view_size.width(), view_size.height()); | 210 bool updated = false; |
| 211 if (clip_area_ != clip_area) { |
| 212 updated = true; |
271 | 213 |
272 graphics2d_ = pp::Graphics2D(instance_, pp_size, true); | 214 // YUV to RGB conversion may require even X and Y coordinates for |
273 if (!instance_->BindGraphics(graphics2d_)) { | 215 // the top left corner of the clipping area. |
274 LOG(ERROR) << "Couldn't bind the device context."; | 216 clip_area_ = AlignRect(clip_area); |
275 return false; | 217 clip_area_.intersect(SkIRect::MakeSize(view_size)); |
276 } | 218 } |
277 | 219 |
278 if (view_size.isEmpty()) | 220 if (view_size_ != view_size) { |
279 return false; | 221 updated = true; |
| 222 view_size_ = view_size; |
280 | 223 |
281 // Allocate the backing store to save the desktop image. | 224 pp::Size pp_size = pp::Size(view_size.width(), view_size.height()); |
282 if ((backing_store_.get() == NULL) || | 225 graphics2d_ = pp::Graphics2D(instance_, pp_size, true); |
283 (backing_store_->size() != pp_size)) { | 226 bool result = instance_->BindGraphics(graphics2d_); |
284 VLOG(1) << "Allocate backing store: " | 227 |
285 << view_size.width() << " x " << view_size.height(); | 228 // There is no good way to handle this error currently. |
286 backing_store_.reset( | 229 DCHECK(result) << "Couldn't bind the device context."; |
287 new pp::ImageData(instance_, pp::ImageData::GetNativeImageDataFormat(), | |
288 pp_size, false)); | |
289 DCHECK(backing_store_.get() && !backing_store_->is_null()) | |
290 << "Not enough memory for backing store."; | |
291 } | 230 } |
292 return true; | 231 |
| 232 view_changed_ = view_changed_ || updated; |
| 233 return updated; |
293 } | 234 } |
294 | 235 |
295 void PepperView::AllocateFrame(media::VideoFrame::Format format, | 236 void PepperView::OnFrameReady(const SkISize& screen_size, |
296 const SkISize& size, | 237 SkISize* view_size_out, |
297 scoped_refptr<media::VideoFrame>* frame_out, | 238 SkIRect* clip_area_out, |
298 const base::Closure& done) { | 239 scoped_ptr<pp::ImageData>* backing_store_out, |
| 240 const base::Closure& done) { |
299 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); | 241 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); |
300 | 242 |
301 *frame_out = media::VideoFrame::CreateFrame( | 243 SetScreenSize(screen_size); |
302 media::VideoFrame::RGB32, size.width(), size.height(), | 244 |
303 base::TimeDelta(), base::TimeDelta()); | 245 // Adjust the backing buffer size accordingly to the clipping area size: |
304 (*frame_out)->AddRef(); | 246 // - no buffer is needed when the clipping area is empty. |
| 247 // - previously allocated buffer can be reused if its size matches. |
| 248 if (clip_area_.isEmpty()) { |
| 249 backing_store_.reset(NULL); |
| 250 } else { |
| 251 pp::Size pp_size = pp::Size(clip_area_.width(), clip_area_.height()); |
| 252 if ((backing_store_.get() == NULL) || |
| 253 (backing_store_->size() != pp_size)) { |
| 254 VLOG(1) << "Allocate backing store: " |
| 255 << clip_area_.width() << " x " << clip_area_.height(); |
| 256 backing_store_.reset( |
| 257 new pp::ImageData(instance_, |
| 258 PP_IMAGEDATAFORMAT_BGRA_PREMUL, |
| 259 pp_size, false)); |
| 260 DCHECK(backing_store_.get() && !backing_store_->is_null()) |
| 261 << "Not enough memory for backing store."; |
| 262 } |
| 263 } |
| 264 |
| 265 // Watch for any changes to the view size or clipping area while drawing. |
| 266 view_changed_ = false; |
| 267 |
| 268 // Capture the drawing parameters and pass them to the caller. |
| 269 *view_size_out = view_size_; |
| 270 *clip_area_out = clip_area_; |
| 271 *backing_store_out = backing_store_.Pass(); |
305 done.Run(); | 272 done.Run(); |
306 } | 273 } |
307 | 274 |
308 void PepperView::ReleaseFrame(media::VideoFrame* frame) { | 275 void PepperView::OnPaintDone(scoped_ptr<pp::ImageData> backing_store, |
| 276 scoped_ptr<SkRegion> updated_region) { |
309 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); | 277 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); |
310 | 278 |
311 if (frame) | 279 // Discard the updated backing store entirely in case if |
312 frame->Release(); | 280 // the clipping area has been changed while the drawing |
| 281 // operation was pending. Next painting operation has been |
| 282 // scheduled in response to the change and it will update |
| 283 // the screen properly. |
| 284 if (view_changed_) { |
| 285 instance_->RefreshFullFrame(); |
| 286 return; |
| 287 } |
| 288 |
| 289 backing_store_ = backing_store.Pass(); |
| 290 |
| 291 // Notify Pepper API about the updated areas and flush pixels to the screen. |
| 292 base::Time start_time = base::Time::Now(); |
| 293 |
| 294 for (SkRegion::Iterator i(*updated_region); !i.done(); i.next()) { |
| 295 SkIRect rect = i.rect(); |
| 296 if (!rect.intersect(clip_area_)) |
| 297 continue; |
| 298 |
| 299 // Specify the rectangle coordinates relative to the clipping area. |
| 300 rect.offset(- clip_area_.left(), - clip_area_.top()); |
| 301 |
| 302 // Pepper Graphics 2D has a strange and badly documented API that the |
| 303 // point here is the offset from the source rect. Why? |
| 304 graphics2d_.PaintImageData( |
| 305 *backing_store_.get(), |
| 306 pp::Point(clip_area_.left(), clip_area_.top()), |
| 307 pp::Rect(rect.left(), rect.top(), rect.width(), rect.height())); |
| 308 } |
| 309 |
| 310 FlushGraphics(start_time); |
| 311 return; |
313 } | 312 } |
314 | 313 |
315 void PepperView::OnPartialFrameOutput(media::VideoFrame* frame, | 314 void PepperView::OnFlushDone(base::Time paint_start) { |
316 SkRegion* region, | |
317 const base::Closure& done) { | |
318 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); | |
319 | |
320 // TODO(ajwong): Clean up this API to be async so we don't need to use a | |
321 // member variable as a hack. | |
322 PaintFrame(frame, *region); | |
323 done.Run(); | |
324 } | |
325 | |
326 void PepperView::OnPaintDone(base::Time paint_start) { | |
327 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); | 315 DCHECK(context_->main_message_loop()->BelongsToCurrentThread()); |
328 instance_->GetStats()->video_paint_ms()->Record( | 316 instance_->GetStats()->video_paint_ms()->Record( |
329 (base::Time::Now() - paint_start).InMilliseconds()); | 317 (base::Time::Now() - paint_start).InMilliseconds()); |
330 | 318 |
331 // If the last flush failed because there was already another one in progress | 319 // If the last flush failed because there was already another one in progress |
332 // then we perform the flush now. | 320 // then we perform the flush now. |
333 if (flush_blocked_) | 321 if (flush_blocked_) |
334 FlushGraphics(base::Time::Now()); | 322 FlushGraphics(base::Time::Now()); // TODO: why is it not |paint_start|? |
335 return; | 323 return; |
336 } | 324 } |
337 | 325 |
338 } // namespace remoting | 326 } // namespace remoting |
OLD | NEW |