1868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved. 2868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 3868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)// found in the LICENSE file. 4868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 5868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "cc/resources/image_raster_worker_pool.h" 6868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 7868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "base/debug/trace_event.h" 8eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "cc/debug/traced_value.h" 9868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)#include "cc/resources/resource.h" 10868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 11868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)namespace cc { 12868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)// static 14a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochscoped_ptr<RasterWorkerPool> ImageRasterWorkerPool::Create( 15a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) base::SequencedTaskRunner* task_runner, 16a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch TaskGraphRunner* task_graph_runner, 17a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch ResourceProvider* resource_provider) { 18a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch return make_scoped_ptr<RasterWorkerPool>(new ImageRasterWorkerPool( 19a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch task_runner, task_graph_runner, resource_provider)); 205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 21868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 22868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)ImageRasterWorkerPool::ImageRasterWorkerPool( 23a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) base::SequencedTaskRunner* task_runner, 24a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch TaskGraphRunner* task_graph_runner, 25a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch ResourceProvider* resource_provider) 26c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch : task_runner_(task_runner), 27c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch task_graph_runner_(task_graph_runner), 28c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch namespace_token_(task_graph_runner->GetNamespaceToken()), 29c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch resource_provider_(resource_provider), 30eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch raster_tasks_pending_(false), 31c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raster_tasks_required_for_activation_pending_(false), 32c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raster_finished_weak_ptr_factory_(this) {} 33868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)ImageRasterWorkerPool::~ImageRasterWorkerPool() {} 35868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 36a02191e04bc25c4935f804f2c080ae28663d096dBen MurdochRasterizer* ImageRasterWorkerPool::AsRasterizer() { return this; } 37a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch 38a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochvoid ImageRasterWorkerPool::SetClient(RasterizerClient* client) { 39c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch client_ = client; 40c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch} 41c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 42c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochvoid ImageRasterWorkerPool::Shutdown() { 43c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch TRACE_EVENT0("cc", "ImageRasterWorkerPool::Shutdown"); 44c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 45a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch TaskGraph empty; 46c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch task_graph_runner_->ScheduleTasks(namespace_token_, &empty); 47c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch task_graph_runner_->WaitForTasksToFinishRunning(namespace_token_); 48c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch} 49c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 50a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)void ImageRasterWorkerPool::ScheduleTasks(RasterTaskQueue* queue) { 51868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) TRACE_EVENT0("cc", "ImageRasterWorkerPool::ScheduleTasks"); 52868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 53a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) DCHECK_EQ(queue->required_for_activation_count, 54a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) static_cast<size_t>( 55a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) std::count_if(queue->items.begin(), 56a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) queue->items.end(), 57a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) RasterTaskQueue::Item::IsRequiredForActivation))); 58a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) 59eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (!raster_tasks_pending_) 60eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch TRACE_EVENT_ASYNC_BEGIN0("cc", "ScheduledTasks", this); 61eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 62eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch raster_tasks_pending_ = true; 63eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch raster_tasks_required_for_activation_pending_ = true; 64eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 655d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) unsigned priority = kRasterTaskPriorityBase; 665d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 675d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) graph_.Reset(); 68eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 69c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch // Cancel existing OnRasterFinished callbacks. 70c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raster_finished_weak_ptr_factory_.InvalidateWeakPtrs(); 71c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 72a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch scoped_refptr<RasterizerTask> 73eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch new_raster_required_for_activation_finished_task( 745d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) CreateRasterRequiredForActivationFinishedTask( 75c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch queue->required_for_activation_count, 76c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch task_runner_.get(), 77c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch base::Bind( 78c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch &ImageRasterWorkerPool::OnRasterRequiredForActivationFinished, 79c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raster_finished_weak_ptr_factory_.GetWeakPtr()))); 80a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch scoped_refptr<RasterizerTask> new_raster_finished_task( 81c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch CreateRasterFinishedTask( 82c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch task_runner_.get(), 83c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch base::Bind(&ImageRasterWorkerPool::OnRasterFinished, 84c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raster_finished_weak_ptr_factory_.GetWeakPtr()))); 855d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 86a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) for (RasterTaskQueue::Item::Vector::const_iterator it = queue->items.begin(); 87a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) it != queue->items.end(); 88a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) ++it) { 89a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) const RasterTaskQueue::Item& item = *it; 90a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch RasterTask* task = item.task; 91d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) DCHECK(!task->HasCompleted()); 925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 93a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) if (item.required_for_activation) { 94a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch graph_.edges.push_back(TaskGraph::Edge( 955d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) task, new_raster_required_for_activation_finished_task.get())); 96868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } 97868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 98c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch InsertNodesForRasterTask(&graph_, task, task->dependencies(), priority++); 995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) graph_.edges.push_back( 101a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch TaskGraph::Edge(task, new_raster_finished_task.get())); 102868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) } 103868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) InsertNodeForTask(&graph_, 1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) new_raster_required_for_activation_finished_task.get(), 1065d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) kRasterRequiredForActivationFinishedTaskPriority, 107a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) queue->required_for_activation_count); 1085d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) InsertNodeForTask(&graph_, 1095d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) new_raster_finished_task.get(), 1105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) kRasterFinishedTaskPriority, 111a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles) queue->items.size()); 1125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 113c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch ScheduleTasksOnOriginThread(this, &graph_); 114c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch task_graph_runner_->ScheduleTasks(namespace_token_, &graph_); 115eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 116c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raster_finished_task_ = new_raster_finished_task; 117c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch raster_required_for_activation_finished_task_ = 118c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch new_raster_required_for_activation_finished_task; 119868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 1201e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) TRACE_EVENT_ASYNC_STEP_INTO1( 1215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) "cc", 1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) "ScheduledTasks", 1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) this, 1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) "rasterizing", 1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) "state", 1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) TracedValue::FromValue(StateAsValue().release())); 127eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch} 128eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 1295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void ImageRasterWorkerPool::CheckForCompletedTasks() { 1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) TRACE_EVENT0("cc", "ImageRasterWorkerPool::CheckForCompletedTasks"); 1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 132c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch task_graph_runner_->CollectCompletedTasks(namespace_token_, 133c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch &completed_tasks_); 134a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch for (Task::Vector::const_iterator it = completed_tasks_.begin(); 1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) it != completed_tasks_.end(); 1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) ++it) { 137a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch RasterizerTask* task = static_cast<RasterizerTask*>(it->get()); 1385d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1395d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) task->WillComplete(); 1405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) task->CompleteOnOriginThread(this); 1415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) task->DidComplete(); 1425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) task->RunReplyOnOriginThread(); 1445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 1455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) completed_tasks_.clear(); 1465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 148a02191e04bc25c4935f804f2c080ae28663d096dBen MurdochSkCanvas* ImageRasterWorkerPool::AcquireCanvasForRaster(RasterTask* task) { 149a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch return resource_provider_->MapImageRasterBuffer(task->resource()->id()); 1505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 152a02191e04bc25c4935f804f2c080ae28663d096dBen Murdochvoid ImageRasterWorkerPool::ReleaseCanvasForRaster(RasterTask* task) { 153a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch resource_provider_->UnmapImageRasterBuffer(task->resource()->id()); 154a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch 155a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch // Map/UnmapImageRasterBuffer provides direct access to the memory used by the 156a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch // GPU. Read lock fences are required to ensure that we're not trying to map a 157a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch // resource that is currently in-use by the GPU. 158a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch resource_provider_->EnableReadLockFences(task->resource()->id(), true); 1595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)} 1605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 161c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochvoid ImageRasterWorkerPool::OnRasterFinished() { 162c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch TRACE_EVENT0("cc", "ImageRasterWorkerPool::OnRasterFinished"); 163c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 164eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch DCHECK(raster_tasks_pending_); 165eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch raster_tasks_pending_ = false; 166eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch TRACE_EVENT_ASYNC_END0("cc", "ScheduledTasks", this); 167c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch client_->DidFinishRunningTasks(); 168eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch} 169eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 170c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochvoid ImageRasterWorkerPool::OnRasterRequiredForActivationFinished() { 171c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch TRACE_EVENT0("cc", 172c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch "ImageRasterWorkerPool::OnRasterRequiredForActivationFinished"); 173c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch 174eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch DCHECK(raster_tasks_required_for_activation_pending_); 175eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch raster_tasks_required_for_activation_pending_ = false; 1761e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) TRACE_EVENT_ASYNC_STEP_INTO1( 1775d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) "cc", 1785d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) "ScheduledTasks", 1795d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) this, 1805d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) "rasterizing", 1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) "state", 1825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) TracedValue::FromValue(StateAsValue().release())); 183c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch client_->DidFinishRunningTasksRequiredForActivation(); 184868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)} 185868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) 186eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochscoped_ptr<base::Value> ImageRasterWorkerPool::StateAsValue() const { 187eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue); 188eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 189eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch state->SetBoolean("tasks_required_for_activation_pending", 190eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch raster_tasks_required_for_activation_pending_); 191eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return state.PassAs<base::Value>(); 192eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch} 193eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 194868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles)} // namespace cc 195