1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "cc/resources/raster_worker_pool.h"
6
7 #include "base/json/json_writer.h"
8 #include "base/metrics/histogram.h"
9 #include "base/values.h"
10 #include "cc/debug/devtools_instrumentation.h"
11 #include "cc/debug/traced_value.h"
12 #include "cc/resources/picture_pile_impl.h"
13 #include "skia/ext/lazy_pixel_ref.h"
14 #include "skia/ext/paint_simplifier.h"
15 #include "third_party/skia/include/core/SkBitmap.h"
16
17 namespace cc {
18
19 namespace {
20
21 // Subclass of Allocator that takes a suitably allocated pointer and uses
22 // it as the pixel memory for the bitmap.
23 class IdentityAllocator : public SkBitmap::Allocator {
24 public:
IdentityAllocator(void * buffer)25 explicit IdentityAllocator(void* buffer) : buffer_(buffer) {}
allocPixelRef(SkBitmap * dst,SkColorTable *)26 virtual bool allocPixelRef(SkBitmap* dst, SkColorTable*) OVERRIDE {
27 dst->setPixels(buffer_);
28 return true;
29 }
30 private:
31 void* buffer_;
32 };
33
34 // Flag to indicate whether we should try and detect that
35 // a tile is of solid color.
36 const bool kUseColorEstimator = true;
37
38 class DisableLCDTextFilter : public SkDrawFilter {
39 public:
40 // SkDrawFilter interface.
filter(SkPaint * paint,SkDrawFilter::Type type)41 virtual bool filter(SkPaint* paint, SkDrawFilter::Type type) OVERRIDE {
42 if (type != SkDrawFilter::kText_Type)
43 return true;
44
45 paint->setLCDRenderText(false);
46 return true;
47 }
48 };
49
50 class RasterWorkerPoolTaskImpl : public internal::RasterWorkerPoolTask {
51 public:
RasterWorkerPoolTaskImpl(const Resource * resource,PicturePileImpl * picture_pile,gfx::Rect content_rect,float contents_scale,RasterMode raster_mode,TileResolution tile_resolution,int layer_id,const void * tile_id,int source_frame_number,RenderingStatsInstrumentation * rendering_stats,const RasterWorkerPool::RasterTask::Reply & reply,TaskVector * dependencies)52 RasterWorkerPoolTaskImpl(const Resource* resource,
53 PicturePileImpl* picture_pile,
54 gfx::Rect content_rect,
55 float contents_scale,
56 RasterMode raster_mode,
57 TileResolution tile_resolution,
58 int layer_id,
59 const void* tile_id,
60 int source_frame_number,
61 RenderingStatsInstrumentation* rendering_stats,
62 const RasterWorkerPool::RasterTask::Reply& reply,
63 TaskVector* dependencies)
64 : internal::RasterWorkerPoolTask(resource, dependencies),
65 picture_pile_(picture_pile),
66 content_rect_(content_rect),
67 contents_scale_(contents_scale),
68 raster_mode_(raster_mode),
69 tile_resolution_(tile_resolution),
70 layer_id_(layer_id),
71 tile_id_(tile_id),
72 source_frame_number_(source_frame_number),
73 rendering_stats_(rendering_stats),
74 reply_(reply) {}
75
RunAnalysisOnThread(unsigned thread_index)76 void RunAnalysisOnThread(unsigned thread_index) {
77 TRACE_EVENT1("cc",
78 "RasterWorkerPoolTaskImpl::RunAnalysisOnThread",
79 "data",
80 TracedValue::FromValue(DataAsValue().release()));
81
82 DCHECK(picture_pile_.get());
83 DCHECK(rendering_stats_);
84
85 PicturePileImpl* picture_clone =
86 picture_pile_->GetCloneForDrawingOnThread(thread_index);
87
88 DCHECK(picture_clone);
89
90 picture_clone->AnalyzeInRect(
91 content_rect_, contents_scale_, &analysis_, rendering_stats_);
92
93 // Record the solid color prediction.
94 UMA_HISTOGRAM_BOOLEAN("Renderer4.SolidColorTilesAnalyzed",
95 analysis_.is_solid_color);
96
97 // Clear the flag if we're not using the estimator.
98 analysis_.is_solid_color &= kUseColorEstimator;
99 }
100
RunRasterOnThread(unsigned thread_index,void * buffer,gfx::Size size,int stride)101 bool RunRasterOnThread(unsigned thread_index,
102 void* buffer,
103 gfx::Size size,
104 int stride) {
105 TRACE_EVENT2(
106 "cc", "RasterWorkerPoolTaskImpl::RunRasterOnThread",
107 "data",
108 TracedValue::FromValue(DataAsValue().release()),
109 "raster_mode",
110 TracedValue::FromValue(RasterModeAsValue(raster_mode_).release()));
111
112 devtools_instrumentation::ScopedLayerTask raster_task(
113 devtools_instrumentation::kRasterTask, layer_id_);
114
115 DCHECK(picture_pile_.get());
116 DCHECK(buffer);
117
118 if (analysis_.is_solid_color)
119 return false;
120
121 PicturePileImpl* picture_clone =
122 picture_pile_->GetCloneForDrawingOnThread(thread_index);
123
124 SkBitmap bitmap;
125 switch (resource()->format()) {
126 case RGBA_4444:
127 // Use the default stride if we will eventually convert this
128 // bitmap to 4444.
129 bitmap.setConfig(SkBitmap::kARGB_8888_Config,
130 size.width(),
131 size.height());
132 bitmap.allocPixels();
133 break;
134 case RGBA_8888:
135 case BGRA_8888:
136 bitmap.setConfig(SkBitmap::kARGB_8888_Config,
137 size.width(),
138 size.height(),
139 stride);
140 bitmap.setPixels(buffer);
141 break;
142 case LUMINANCE_8:
143 case RGB_565:
144 case ETC1:
145 NOTREACHED();
146 break;
147 }
148
149 SkBitmapDevice device(bitmap);
150 SkCanvas canvas(&device);
151 skia::RefPtr<SkDrawFilter> draw_filter;
152 switch (raster_mode_) {
153 case LOW_QUALITY_RASTER_MODE:
154 draw_filter = skia::AdoptRef(new skia::PaintSimplifier);
155 break;
156 case HIGH_QUALITY_NO_LCD_RASTER_MODE:
157 draw_filter = skia::AdoptRef(new DisableLCDTextFilter);
158 break;
159 case HIGH_QUALITY_RASTER_MODE:
160 break;
161 case NUM_RASTER_MODES:
162 default:
163 NOTREACHED();
164 }
165
166 canvas.setDrawFilter(draw_filter.get());
167
168 base::TimeDelta prev_rasterize_time =
169 rendering_stats_->impl_thread_rendering_stats().rasterize_time;
170
171 // Only record rasterization time for highres tiles, because
172 // lowres tiles are not required for activation and therefore
173 // introduce noise in the measurement (sometimes they get rasterized
174 // before we draw and sometimes they aren't)
175 if (tile_resolution_ == HIGH_RESOLUTION) {
176 picture_clone->RasterToBitmap(
177 &canvas, content_rect_, contents_scale_, rendering_stats_);
178 } else {
179 picture_clone->RasterToBitmap(
180 &canvas, content_rect_, contents_scale_, NULL);
181 }
182
183 if (rendering_stats_->record_rendering_stats()) {
184 base::TimeDelta current_rasterize_time =
185 rendering_stats_->impl_thread_rendering_stats().rasterize_time;
186 HISTOGRAM_CUSTOM_COUNTS(
187 "Renderer4.PictureRasterTimeUS",
188 (current_rasterize_time - prev_rasterize_time).InMicroseconds(),
189 0,
190 100000,
191 100);
192 }
193
194 ChangeBitmapConfigIfNeeded(bitmap, buffer);
195
196 return true;
197 }
198
199 // Overridden from internal::RasterWorkerPoolTask:
RunOnWorkerThread(unsigned thread_index,void * buffer,gfx::Size size,int stride)200 virtual bool RunOnWorkerThread(unsigned thread_index,
201 void* buffer,
202 gfx::Size size,
203 int stride)
204 OVERRIDE {
205 RunAnalysisOnThread(thread_index);
206 return RunRasterOnThread(thread_index, buffer, size, stride);
207 }
CompleteOnOriginThread()208 virtual void CompleteOnOriginThread() OVERRIDE {
209 reply_.Run(analysis_, !HasFinishedRunning() || WasCanceled());
210 }
211
212 protected:
~RasterWorkerPoolTaskImpl()213 virtual ~RasterWorkerPoolTaskImpl() {}
214
215 private:
DataAsValue() const216 scoped_ptr<base::Value> DataAsValue() const {
217 scoped_ptr<base::DictionaryValue> res(new base::DictionaryValue());
218 res->Set("tile_id", TracedValue::CreateIDRef(tile_id_).release());
219 res->Set("resolution", TileResolutionAsValue(tile_resolution_).release());
220 res->SetInteger("source_frame_number", source_frame_number_);
221 res->SetInteger("layer_id", layer_id_);
222 return res.PassAs<base::Value>();
223 }
224
ChangeBitmapConfigIfNeeded(const SkBitmap & bitmap,void * buffer)225 void ChangeBitmapConfigIfNeeded(const SkBitmap& bitmap,
226 void* buffer) {
227 TRACE_EVENT0("cc", "RasterWorkerPoolTaskImpl::ChangeBitmapConfigIfNeeded");
228 SkBitmap::Config config = SkBitmapConfig(resource()->format());
229 if (bitmap.getConfig() != config) {
230 SkBitmap bitmap_dest;
231 IdentityAllocator allocator(buffer);
232 bitmap.copyTo(&bitmap_dest, config, &allocator);
233 // TODO(kaanb): The GL pipeline assumes a 4-byte alignment for the
234 // bitmap data. This check will be removed once crbug.com/293728 is fixed.
235 CHECK_EQ(0u, bitmap_dest.rowBytes() % 4);
236 }
237 }
238
239 PicturePileImpl::Analysis analysis_;
240 scoped_refptr<PicturePileImpl> picture_pile_;
241 gfx::Rect content_rect_;
242 float contents_scale_;
243 RasterMode raster_mode_;
244 TileResolution tile_resolution_;
245 int layer_id_;
246 const void* tile_id_;
247 int source_frame_number_;
248 RenderingStatsInstrumentation* rendering_stats_;
249 const RasterWorkerPool::RasterTask::Reply reply_;
250
251 DISALLOW_COPY_AND_ASSIGN(RasterWorkerPoolTaskImpl);
252 };
253
254 class ImageDecodeWorkerPoolTaskImpl : public internal::WorkerPoolTask {
255 public:
ImageDecodeWorkerPoolTaskImpl(skia::LazyPixelRef * pixel_ref,int layer_id,RenderingStatsInstrumentation * rendering_stats,const RasterWorkerPool::Task::Reply & reply)256 ImageDecodeWorkerPoolTaskImpl(skia::LazyPixelRef* pixel_ref,
257 int layer_id,
258 RenderingStatsInstrumentation* rendering_stats,
259 const RasterWorkerPool::Task::Reply& reply)
260 : pixel_ref_(skia::SharePtr(pixel_ref)),
261 layer_id_(layer_id),
262 rendering_stats_(rendering_stats),
263 reply_(reply) {}
264
265 // Overridden from internal::WorkerPoolTask:
RunOnWorkerThread(unsigned thread_index)266 virtual void RunOnWorkerThread(unsigned thread_index) OVERRIDE {
267 TRACE_EVENT0("cc", "ImageDecodeWorkerPoolTaskImpl::RunOnWorkerThread");
268 devtools_instrumentation::ScopedImageDecodeTask image_decode_task(
269 pixel_ref_.get());
270 pixel_ref_->Decode();
271 }
CompleteOnOriginThread()272 virtual void CompleteOnOriginThread() OVERRIDE {
273 reply_.Run(!HasFinishedRunning());
274 }
275
276 protected:
~ImageDecodeWorkerPoolTaskImpl()277 virtual ~ImageDecodeWorkerPoolTaskImpl() {}
278
279 private:
280 skia::RefPtr<skia::LazyPixelRef> pixel_ref_;
281 int layer_id_;
282 RenderingStatsInstrumentation* rendering_stats_;
283 const RasterWorkerPool::Task::Reply reply_;
284
285 DISALLOW_COPY_AND_ASSIGN(ImageDecodeWorkerPoolTaskImpl);
286 };
287
288 class RasterFinishedWorkerPoolTaskImpl : public internal::WorkerPoolTask {
289 public:
290 typedef base::Callback<void(const internal::WorkerPoolTask* source)>
291 Callback;
292
RasterFinishedWorkerPoolTaskImpl(const Callback & on_raster_finished_callback)293 RasterFinishedWorkerPoolTaskImpl(
294 const Callback& on_raster_finished_callback)
295 : origin_loop_(base::MessageLoopProxy::current().get()),
296 on_raster_finished_callback_(on_raster_finished_callback) {
297 }
298
299 // Overridden from internal::WorkerPoolTask:
RunOnWorkerThread(unsigned thread_index)300 virtual void RunOnWorkerThread(unsigned thread_index) OVERRIDE {
301 TRACE_EVENT0("cc", "RasterFinishedWorkerPoolTaskImpl::RunOnWorkerThread");
302 origin_loop_->PostTask(
303 FROM_HERE,
304 base::Bind(&RasterFinishedWorkerPoolTaskImpl::RunOnOriginThread,
305 this));
306 }
CompleteOnOriginThread()307 virtual void CompleteOnOriginThread() OVERRIDE {}
308
309 private:
~RasterFinishedWorkerPoolTaskImpl()310 virtual ~RasterFinishedWorkerPoolTaskImpl() {}
311
RunOnOriginThread() const312 void RunOnOriginThread() const {
313 on_raster_finished_callback_.Run(this);
314 }
315
316 scoped_refptr<base::MessageLoopProxy> origin_loop_;
317 const Callback on_raster_finished_callback_;
318
319 DISALLOW_COPY_AND_ASSIGN(RasterFinishedWorkerPoolTaskImpl);
320 };
321
322 const char* kWorkerThreadNamePrefix = "CompositorRaster";
323
324 } // namespace
325
326 namespace internal {
327
RasterWorkerPoolTask(const Resource * resource,TaskVector * dependencies)328 RasterWorkerPoolTask::RasterWorkerPoolTask(
329 const Resource* resource, TaskVector* dependencies)
330 : did_run_(false),
331 did_complete_(false),
332 was_canceled_(false),
333 resource_(resource) {
334 dependencies_.swap(*dependencies);
335 }
336
~RasterWorkerPoolTask()337 RasterWorkerPoolTask::~RasterWorkerPoolTask() {
338 }
339
DidRun(bool was_canceled)340 void RasterWorkerPoolTask::DidRun(bool was_canceled) {
341 DCHECK(!did_run_);
342 did_run_ = true;
343 was_canceled_ = was_canceled;
344 }
345
HasFinishedRunning() const346 bool RasterWorkerPoolTask::HasFinishedRunning() const {
347 return did_run_;
348 }
349
WasCanceled() const350 bool RasterWorkerPoolTask::WasCanceled() const {
351 return was_canceled_;
352 }
353
WillComplete()354 void RasterWorkerPoolTask::WillComplete() {
355 DCHECK(!did_complete_);
356 }
357
DidComplete()358 void RasterWorkerPoolTask::DidComplete() {
359 DCHECK(!did_complete_);
360 did_complete_ = true;
361 }
362
HasCompleted() const363 bool RasterWorkerPoolTask::HasCompleted() const {
364 return did_complete_;
365 }
366
367 } // namespace internal
368
Set()369 RasterWorkerPool::Task::Set::Set() {
370 }
371
~Set()372 RasterWorkerPool::Task::Set::~Set() {
373 }
374
Insert(const Task & task)375 void RasterWorkerPool::Task::Set::Insert(const Task& task) {
376 DCHECK(!task.is_null());
377 tasks_.push_back(task.internal_);
378 }
379
Task()380 RasterWorkerPool::Task::Task() {
381 }
382
Task(internal::WorkerPoolTask * internal)383 RasterWorkerPool::Task::Task(internal::WorkerPoolTask* internal)
384 : internal_(internal) {
385 }
386
~Task()387 RasterWorkerPool::Task::~Task() {
388 }
389
Reset()390 void RasterWorkerPool::Task::Reset() {
391 internal_ = NULL;
392 }
393
Queue()394 RasterWorkerPool::RasterTask::Queue::Queue() {
395 }
396
~Queue()397 RasterWorkerPool::RasterTask::Queue::~Queue() {
398 }
399
Append(const RasterTask & task,bool required_for_activation)400 void RasterWorkerPool::RasterTask::Queue::Append(
401 const RasterTask& task, bool required_for_activation) {
402 DCHECK(!task.is_null());
403 tasks_.push_back(task.internal_);
404 if (required_for_activation)
405 tasks_required_for_activation_.insert(task.internal_.get());
406 }
407
RasterTask()408 RasterWorkerPool::RasterTask::RasterTask() {
409 }
410
RasterTask(internal::RasterWorkerPoolTask * internal)411 RasterWorkerPool::RasterTask::RasterTask(
412 internal::RasterWorkerPoolTask* internal)
413 : internal_(internal) {
414 }
415
Reset()416 void RasterWorkerPool::RasterTask::Reset() {
417 internal_ = NULL;
418 }
419
~RasterTask()420 RasterWorkerPool::RasterTask::~RasterTask() {
421 }
422
423 // static
CreateRasterTask(const Resource * resource,PicturePileImpl * picture_pile,gfx::Rect content_rect,float contents_scale,RasterMode raster_mode,TileResolution tile_resolution,int layer_id,const void * tile_id,int source_frame_number,RenderingStatsInstrumentation * rendering_stats,const RasterTask::Reply & reply,Task::Set * dependencies)424 RasterWorkerPool::RasterTask RasterWorkerPool::CreateRasterTask(
425 const Resource* resource,
426 PicturePileImpl* picture_pile,
427 gfx::Rect content_rect,
428 float contents_scale,
429 RasterMode raster_mode,
430 TileResolution tile_resolution,
431 int layer_id,
432 const void* tile_id,
433 int source_frame_number,
434 RenderingStatsInstrumentation* rendering_stats,
435 const RasterTask::Reply& reply,
436 Task::Set* dependencies) {
437 return RasterTask(
438 new RasterWorkerPoolTaskImpl(resource,
439 picture_pile,
440 content_rect,
441 contents_scale,
442 raster_mode,
443 tile_resolution,
444 layer_id,
445 tile_id,
446 source_frame_number,
447 rendering_stats,
448 reply,
449 &dependencies->tasks_));
450 }
451
452 // static
CreateImageDecodeTask(skia::LazyPixelRef * pixel_ref,int layer_id,RenderingStatsInstrumentation * stats_instrumentation,const Task::Reply & reply)453 RasterWorkerPool::Task RasterWorkerPool::CreateImageDecodeTask(
454 skia::LazyPixelRef* pixel_ref,
455 int layer_id,
456 RenderingStatsInstrumentation* stats_instrumentation,
457 const Task::Reply& reply) {
458 return Task(new ImageDecodeWorkerPoolTaskImpl(pixel_ref,
459 layer_id,
460 stats_instrumentation,
461 reply));
462 }
463
RasterWorkerPool(ResourceProvider * resource_provider,size_t num_threads)464 RasterWorkerPool::RasterWorkerPool(ResourceProvider* resource_provider,
465 size_t num_threads)
466 : WorkerPool(num_threads, kWorkerThreadNamePrefix),
467 client_(NULL),
468 resource_provider_(resource_provider),
469 weak_ptr_factory_(this) {
470 }
471
~RasterWorkerPool()472 RasterWorkerPool::~RasterWorkerPool() {
473 }
474
SetClient(RasterWorkerPoolClient * client)475 void RasterWorkerPool::SetClient(RasterWorkerPoolClient* client) {
476 client_ = client;
477 }
478
Shutdown()479 void RasterWorkerPool::Shutdown() {
480 raster_tasks_.clear();
481 TaskGraph empty;
482 SetTaskGraph(&empty);
483 WorkerPool::Shutdown();
484 weak_ptr_factory_.InvalidateWeakPtrs();
485 }
486
SetRasterTasks(RasterTask::Queue * queue)487 void RasterWorkerPool::SetRasterTasks(RasterTask::Queue* queue) {
488 raster_tasks_.swap(queue->tasks_);
489 raster_tasks_required_for_activation_.swap(
490 queue->tasks_required_for_activation_);
491 }
492
IsRasterTaskRequiredForActivation(internal::RasterWorkerPoolTask * task) const493 bool RasterWorkerPool::IsRasterTaskRequiredForActivation(
494 internal::RasterWorkerPoolTask* task) const {
495 return
496 raster_tasks_required_for_activation_.find(task) !=
497 raster_tasks_required_for_activation_.end();
498 }
499
500 scoped_refptr<internal::WorkerPoolTask>
CreateRasterFinishedTask()501 RasterWorkerPool::CreateRasterFinishedTask() {
502 return make_scoped_refptr(
503 new RasterFinishedWorkerPoolTaskImpl(
504 base::Bind(&RasterWorkerPool::OnRasterFinished,
505 weak_ptr_factory_.GetWeakPtr())));
506 }
507
508 scoped_refptr<internal::WorkerPoolTask>
CreateRasterRequiredForActivationFinishedTask()509 RasterWorkerPool::CreateRasterRequiredForActivationFinishedTask() {
510 return make_scoped_refptr(
511 new RasterFinishedWorkerPoolTaskImpl(
512 base::Bind(&RasterWorkerPool::OnRasterRequiredForActivationFinished,
513 weak_ptr_factory_.GetWeakPtr())));
514 }
515
OnRasterFinished(const internal::WorkerPoolTask * source)516 void RasterWorkerPool::OnRasterFinished(
517 const internal::WorkerPoolTask* source) {
518 TRACE_EVENT0("cc", "RasterWorkerPool::OnRasterFinished");
519
520 // Early out if current |raster_finished_task_| is not the source.
521 if (source != raster_finished_task_.get())
522 return;
523
524 OnRasterTasksFinished();
525 }
526
OnRasterRequiredForActivationFinished(const internal::WorkerPoolTask * source)527 void RasterWorkerPool::OnRasterRequiredForActivationFinished(
528 const internal::WorkerPoolTask* source) {
529 TRACE_EVENT0("cc", "RasterWorkerPool::OnRasterRequiredForActivationFinished");
530
531 // Early out if current |raster_required_for_activation_finished_task_|
532 // is not the source.
533 if (source != raster_required_for_activation_finished_task_.get())
534 return;
535
536 OnRasterTasksRequiredForActivationFinished();
537 }
538
ScheduledStateAsValue() const539 scoped_ptr<base::Value> RasterWorkerPool::ScheduledStateAsValue() const {
540 scoped_ptr<base::DictionaryValue> scheduled_state(new base::DictionaryValue);
541 scheduled_state->SetInteger("task_count", raster_tasks_.size());
542 scheduled_state->SetInteger("task_required_for_activation_count",
543 raster_tasks_required_for_activation_.size());
544 return scheduled_state.PassAs<base::Value>();
545 }
546
547 // static
CreateGraphNodeForTask(internal::WorkerPoolTask * task,unsigned priority,TaskGraph * graph)548 internal::GraphNode* RasterWorkerPool::CreateGraphNodeForTask(
549 internal::WorkerPoolTask* task,
550 unsigned priority,
551 TaskGraph* graph) {
552 internal::GraphNode* node = new internal::GraphNode(task, priority);
553 DCHECK(graph->find(task) == graph->end());
554 graph->set(task, make_scoped_ptr(node));
555 return node;
556 }
557
558 // static
CreateGraphNodeForRasterTask(internal::WorkerPoolTask * raster_task,const TaskVector & decode_tasks,unsigned priority,TaskGraph * graph)559 internal::GraphNode* RasterWorkerPool::CreateGraphNodeForRasterTask(
560 internal::WorkerPoolTask* raster_task,
561 const TaskVector& decode_tasks,
562 unsigned priority,
563 TaskGraph* graph) {
564 DCHECK(!raster_task->HasCompleted());
565
566 internal::GraphNode* raster_node = CreateGraphNodeForTask(
567 raster_task, priority, graph);
568
569 // Insert image decode tasks.
570 for (TaskVector::const_iterator it = decode_tasks.begin();
571 it != decode_tasks.end(); ++it) {
572 internal::WorkerPoolTask* decode_task = it->get();
573
574 // Skip if already decoded.
575 if (decode_task->HasCompleted())
576 continue;
577
578 raster_node->add_dependency();
579
580 // Check if decode task already exists in graph.
581 GraphNodeMap::iterator decode_it = graph->find(decode_task);
582 if (decode_it != graph->end()) {
583 internal::GraphNode* decode_node = decode_it->second;
584 decode_node->add_dependent(raster_node);
585 continue;
586 }
587
588 internal::GraphNode* decode_node = CreateGraphNodeForTask(
589 decode_task, priority, graph);
590 decode_node->add_dependent(raster_node);
591 }
592
593 return raster_node;
594 }
595
596 } // namespace cc
597