prune resources in MemoryCache
[chromium-blink-merge.git] / ppapi / proxy / ppb_image_data_proxy.cc
blob4ed4442ceb4c4a8512f6cd6032c54190153df73f
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
3 // found in the LICENSE file.
5 #include "ppapi/proxy/ppb_image_data_proxy.h"
7 #include <string.h> // For memcpy
9 #include <map>
10 #include <vector>
12 #include "base/logging.h"
13 #include "base/memory/singleton.h"
14 #include "base/memory/weak_ptr.h"
15 #include "build/build_config.h"
16 #include "ppapi/c/pp_completion_callback.h"
17 #include "ppapi/c/pp_errors.h"
18 #include "ppapi/c/pp_resource.h"
19 #include "ppapi/proxy/enter_proxy.h"
20 #include "ppapi/proxy/host_dispatcher.h"
21 #include "ppapi/proxy/plugin_dispatcher.h"
22 #include "ppapi/proxy/plugin_globals.h"
23 #include "ppapi/proxy/plugin_resource_tracker.h"
24 #include "ppapi/proxy/ppapi_messages.h"
25 #include "ppapi/shared_impl/host_resource.h"
26 #include "ppapi/shared_impl/proxy_lock.h"
27 #include "ppapi/shared_impl/resource.h"
28 #include "ppapi/shared_impl/scoped_pp_resource.h"
29 #include "ppapi/thunk/enter.h"
30 #include "ppapi/thunk/thunk.h"
32 #if !defined(OS_NACL)
33 #include "skia/ext/platform_canvas.h"
34 #include "ui/surface/transport_dib.h"
35 #endif
37 using ppapi::thunk::PPB_ImageData_API;
39 namespace ppapi {
40 namespace proxy {
42 namespace {
44 // How ImageData re-use works
45 // --------------------------
47 // When animating plugins (like video), re-creating image datas for each frame
48 // and mapping the memory has a high overhead. So we try to re-use these when
49 // possible.
51 // 1. Plugin makes an asynchronous call that transfers an ImageData to the
52 // implementation of some API.
53 // 2. Plugin frees its ImageData reference. If it doesn't do this we can't
54 // re-use it.
55 // 3. When the last plugin ref of an ImageData is released, we don't actually
56 // delete it. Instead we put it on a queue where we hold onto it in the
57 // plugin process for a short period of time.
58 // 4. The API implementation that received the ImageData finishes using it.
59 // Without our caching system it would get deleted at this point.
60 // 5. The proxy in the renderer will send NotifyUnusedImageData back to the
61 // plugin process. We check if the given resource is in the queue and mark
62 // it as usable.
63 // 6. When the plugin requests a new image data, we check our queue and if there
64 // is a usable ImageData of the right size and format, we'll return it
65 // instead of making a new one. It's important that caching is only requested
66 // when the size is unlikely to change, so cache hits are high.
68 // Some notes:
70 // - We only re-use image data when the plugin and host are rapidly exchanging
71 // them and the size is likely to remain constant. It should be clear that
72 // the plugin is promising that it's done with the image.
74 // - Theoretically we could re-use them in other cases but the lifetime
75 // becomes more difficult to manage. The plugin could have used an ImageData
76 // in an arbitrary number of queued up PaintImageData calls which we would
77 // have to check.
79 // - If a flush takes a long time or there are many released image datas
80 // accumulating in our queue such that some are deleted, we will have
81 // released our reference by the time the renderer notifies us of an unused
82 // image data. In this case we just give up.
84 // - We maintain a per-instance cache. Some pages have many instances of
85 // Flash, for example, each of a different size. If they're all animating we
86 // want each to get its own image data re-use.
88 // - We generate new resource IDs when re-use happens to try to avoid weird
89 // problems if the plugin messes up its refcounting.
91 // Keep a cache entry for this many seconds before expiring it. We get an entry
92 // back from the renderer after an ImageData is swapped out, so it means the
93 // plugin has to be painting at least two frames for this time interval to
94 // get caching.
95 static const int kMaxAgeSeconds = 2;
97 // ImageDataCacheEntry ---------------------------------------------------------
99 struct ImageDataCacheEntry {
100 ImageDataCacheEntry() : added_time(), usable(false), image() {}
101 ImageDataCacheEntry(ImageData* i)
102 : added_time(base::TimeTicks::Now()),
103 usable(false),
104 image(i) {
107 base::TimeTicks added_time;
109 // Set to true when the renderer tells us that it's OK to re-use this iamge.
110 bool usable;
112 scoped_refptr<ImageData> image;
115 // ImageDataInstanceCache ------------------------------------------------------
117 // Per-instance cache of image datas.
118 class ImageDataInstanceCache {
119 public:
120 ImageDataInstanceCache() : next_insertion_point_(0) {}
122 // These functions have the same spec as the ones in ImageDataCache.
123 scoped_refptr<ImageData> Get(PPB_ImageData_Shared::ImageDataType type,
124 int width, int height,
125 PP_ImageDataFormat format);
126 void Add(ImageData* image_data);
127 void ImageDataUsable(ImageData* image_data);
129 // Expires old entries. Returns true if there are still entries in the list,
130 // false if this instance cache is now empty.
131 bool ExpireEntries();
133 private:
134 void IncrementInsertionPoint();
136 // We'll store this many ImageDatas per instance.
137 const static int kCacheSize = 2;
139 ImageDataCacheEntry images_[kCacheSize];
141 // Index into cache where the next item will go.
142 int next_insertion_point_;
145 scoped_refptr<ImageData> ImageDataInstanceCache::Get(
146 PPB_ImageData_Shared::ImageDataType type,
147 int width, int height,
148 PP_ImageDataFormat format) {
149 // Just do a brute-force search since the cache is so small.
150 for (int i = 0; i < kCacheSize; i++) {
151 if (!images_[i].usable)
152 continue;
153 if (images_[i].image->type() != type)
154 continue;
155 const PP_ImageDataDesc& desc = images_[i].image->desc();
156 if (desc.format == format &&
157 desc.size.width == width && desc.size.height == height) {
158 scoped_refptr<ImageData> ret(images_[i].image);
159 images_[i] = ImageDataCacheEntry();
161 // Since we just removed an item, this entry is the best place to insert
162 // a subsequent one.
163 next_insertion_point_ = i;
164 return ret;
167 return scoped_refptr<ImageData>();
170 void ImageDataInstanceCache::Add(ImageData* image_data) {
171 images_[next_insertion_point_] = ImageDataCacheEntry(image_data);
172 IncrementInsertionPoint();
175 void ImageDataInstanceCache::ImageDataUsable(ImageData* image_data) {
176 for (int i = 0; i < kCacheSize; i++) {
177 if (images_[i].image.get() == image_data) {
178 images_[i].usable = true;
180 // This test is important. The renderer doesn't guarantee how many image
181 // datas it has or when it notifies us when one is usable. Its possible
182 // to get into situations where it's always telling us the old one is
183 // usable, and then the older one immediately gets expired. Therefore,
184 // if the next insertion would overwrite this now-usable entry, make the
185 // next insertion overwrite some other entry to avoid the replacement.
186 if (next_insertion_point_ == i)
187 IncrementInsertionPoint();
188 return;
193 bool ImageDataInstanceCache::ExpireEntries() {
194 base::TimeTicks threshold_time =
195 base::TimeTicks::Now() - base::TimeDelta::FromSeconds(kMaxAgeSeconds);
197 bool has_entry = false;
198 for (int i = 0; i < kCacheSize; i++) {
199 if (images_[i].image.get()) {
200 // Entry present.
201 if (images_[i].added_time <= threshold_time) {
202 // Found an entry to expire.
203 images_[i] = ImageDataCacheEntry();
204 next_insertion_point_ = i;
205 } else {
206 // Found an entry that we're keeping.
207 has_entry = true;
211 return has_entry;
214 void ImageDataInstanceCache::IncrementInsertionPoint() {
215 // Go to the next location, wrapping around to get LRU.
216 next_insertion_point_++;
217 if (next_insertion_point_ >= kCacheSize)
218 next_insertion_point_ = 0;
221 // ImageDataCache --------------------------------------------------------------
223 class ImageDataCache {
224 public:
225 ImageDataCache() : weak_factory_(this) {}
226 ~ImageDataCache() {}
228 static ImageDataCache* GetInstance();
230 // Retrieves an image data from the cache of the specified type, size and
231 // format if one exists. If one doesn't exist, this will return a null refptr.
232 scoped_refptr<ImageData> Get(PP_Instance instance,
233 PPB_ImageData_Shared::ImageDataType type,
234 int width, int height,
235 PP_ImageDataFormat format);
237 // Adds the given image data to the cache. There should be no plugin
238 // references to it. This may delete an older item from the cache.
239 void Add(ImageData* image_data);
241 // Notification from the renderer that the given image data is usable.
242 void ImageDataUsable(ImageData* image_data);
244 void DidDeleteInstance(PP_Instance instance);
246 private:
247 friend struct LeakySingletonTraits<ImageDataCache>;
249 // Timer callback to expire entries for the given instance.
250 void OnTimer(PP_Instance instance);
252 typedef std::map<PP_Instance, ImageDataInstanceCache> CacheMap;
253 CacheMap cache_;
255 // This class does timer calls and we don't want to run these outside of the
256 // scope of the object. Technically, since this class is a leaked static,
257 // this will never happen and this factory is unnecessary. However, it's
258 // probably better not to make assumptions about the lifetime of this class.
259 base::WeakPtrFactory<ImageDataCache> weak_factory_;
261 DISALLOW_COPY_AND_ASSIGN(ImageDataCache);
264 // static
265 ImageDataCache* ImageDataCache::GetInstance() {
266 return Singleton<ImageDataCache,
267 LeakySingletonTraits<ImageDataCache> >::get();
270 scoped_refptr<ImageData> ImageDataCache::Get(
271 PP_Instance instance,
272 PPB_ImageData_Shared::ImageDataType type,
273 int width, int height,
274 PP_ImageDataFormat format) {
275 CacheMap::iterator found = cache_.find(instance);
276 if (found == cache_.end())
277 return scoped_refptr<ImageData>();
278 return found->second.Get(type, width, height, format);
281 void ImageDataCache::Add(ImageData* image_data) {
282 cache_[image_data->pp_instance()].Add(image_data);
284 // Schedule a timer to invalidate this entry.
285 base::MessageLoop::current()->PostDelayedTask(
286 FROM_HERE,
287 RunWhileLocked(base::Bind(&ImageDataCache::OnTimer,
288 weak_factory_.GetWeakPtr(),
289 image_data->pp_instance())),
290 base::TimeDelta::FromSeconds(kMaxAgeSeconds));
293 void ImageDataCache::ImageDataUsable(ImageData* image_data) {
294 CacheMap::iterator found = cache_.find(image_data->pp_instance());
295 if (found != cache_.end())
296 found->second.ImageDataUsable(image_data);
299 void ImageDataCache::DidDeleteInstance(PP_Instance instance) {
300 cache_.erase(instance);
303 void ImageDataCache::OnTimer(PP_Instance instance) {
304 CacheMap::iterator found = cache_.find(instance);
305 if (found == cache_.end())
306 return;
307 if (!found->second.ExpireEntries()) {
308 // There are no more entries for this instance, remove it from the cache.
309 cache_.erase(found);
313 } // namespace
315 // ImageData -------------------------------------------------------------------
317 ImageData::ImageData(const HostResource& resource,
318 PPB_ImageData_Shared::ImageDataType type,
319 const PP_ImageDataDesc& desc)
320 : Resource(OBJECT_IS_PROXY, resource),
321 type_(type),
322 desc_(desc),
323 is_candidate_for_reuse_(false) {
326 ImageData::~ImageData() {
329 PPB_ImageData_API* ImageData::AsPPB_ImageData_API() {
330 return this;
333 void ImageData::LastPluginRefWasDeleted() {
334 // The plugin no longer needs this ImageData, add it to our cache if it's
335 // been used in a ReplaceContents. These are the ImageDatas that the renderer
336 // will send back ImageDataUsable messages for.
337 if (is_candidate_for_reuse_)
338 ImageDataCache::GetInstance()->Add(this);
341 void ImageData::InstanceWasDeleted() {
342 ImageDataCache::GetInstance()->DidDeleteInstance(pp_instance());
345 PP_Bool ImageData::Describe(PP_ImageDataDesc* desc) {
346 memcpy(desc, &desc_, sizeof(PP_ImageDataDesc));
347 return PP_TRUE;
350 int32_t ImageData::GetSharedMemory(int* /* handle */,
351 uint32_t* /* byte_count */) {
352 // Not supported in the proxy (this method is for actually implementing the
353 // proxy in the host).
354 return PP_ERROR_NOACCESS;
357 void ImageData::SetIsCandidateForReuse() {
358 is_candidate_for_reuse_ = true;
361 void ImageData::RecycleToPlugin(bool zero_contents) {
362 is_candidate_for_reuse_ = false;
363 if (zero_contents) {
364 void* data = Map();
365 memset(data, 0, desc_.stride * desc_.size.height);
366 Unmap();
370 // PlatformImageData -----------------------------------------------------------
372 #if !defined(OS_NACL)
373 PlatformImageData::PlatformImageData(const HostResource& resource,
374 const PP_ImageDataDesc& desc,
375 ImageHandle handle)
376 : ImageData(resource, PPB_ImageData_Shared::PLATFORM, desc) {
377 #if defined(OS_WIN)
378 transport_dib_.reset(TransportDIB::CreateWithHandle(handle));
379 #else
380 transport_dib_.reset(TransportDIB::Map(handle));
381 #endif // defined(OS_WIN)
384 PlatformImageData::~PlatformImageData() {
387 void* PlatformImageData::Map() {
388 if (!mapped_canvas_.get()) {
389 mapped_canvas_.reset(transport_dib_->GetPlatformCanvas(desc_.size.width,
390 desc_.size.height));
391 if (!mapped_canvas_.get())
392 return NULL;
394 const SkBitmap& bitmap =
395 skia::GetTopDevice(*mapped_canvas_)->accessBitmap(true);
397 bitmap.lockPixels();
398 return bitmap.getAddr(0, 0);
401 void PlatformImageData::Unmap() {
402 // TODO(brettw) have a way to unmap a TransportDIB. Currently this isn't
403 // possible since deleting the TransportDIB also frees all the handles.
404 // We need to add a method to TransportDIB to release the handles.
407 SkCanvas* PlatformImageData::GetPlatformCanvas() {
408 return mapped_canvas_.get();
411 SkCanvas* PlatformImageData::GetCanvas() {
412 return mapped_canvas_.get();
415 // static
416 ImageHandle PlatformImageData::NullHandle() {
417 #if defined(OS_WIN)
418 return NULL;
419 #else
420 return ImageHandle();
421 #endif
424 ImageHandle PlatformImageData::HandleFromInt(int32_t i) {
425 #if defined(OS_WIN)
426 return reinterpret_cast<ImageHandle>(i);
427 #else
428 return ImageHandle(i, false);
429 #endif
431 #endif // !defined(OS_NACL)
433 // SimpleImageData -------------------------------------------------------------
435 SimpleImageData::SimpleImageData(const HostResource& resource,
436 const PP_ImageDataDesc& desc,
437 const base::SharedMemoryHandle& handle)
438 : ImageData(resource, PPB_ImageData_Shared::SIMPLE, desc),
439 shm_(handle, false /* read_only */),
440 size_(desc.size.width * desc.size.height * 4),
441 map_count_(0) {
444 SimpleImageData::~SimpleImageData() {
447 void* SimpleImageData::Map() {
448 if (map_count_++ == 0)
449 shm_.Map(size_);
450 return shm_.memory();
453 void SimpleImageData::Unmap() {
454 if (--map_count_ == 0)
455 shm_.Unmap();
458 SkCanvas* SimpleImageData::GetPlatformCanvas() {
459 return NULL; // No canvas available.
462 SkCanvas* SimpleImageData::GetCanvas() {
463 return NULL; // No canvas available.
466 // PPB_ImageData_Proxy ---------------------------------------------------------
468 PPB_ImageData_Proxy::PPB_ImageData_Proxy(Dispatcher* dispatcher)
469 : InterfaceProxy(dispatcher) {
472 PPB_ImageData_Proxy::~PPB_ImageData_Proxy() {
475 // static
476 PP_Resource PPB_ImageData_Proxy::CreateProxyResource(
477 PP_Instance instance,
478 PPB_ImageData_Shared::ImageDataType type,
479 PP_ImageDataFormat format,
480 const PP_Size& size,
481 PP_Bool init_to_zero) {
482 PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
483 if (!dispatcher)
484 return 0;
486 // Check the cache.
487 scoped_refptr<ImageData> cached_image_data =
488 ImageDataCache::GetInstance()->Get(instance, type,
489 size.width, size.height, format);
490 if (cached_image_data.get()) {
491 // We have one we can re-use rather than allocating a new one.
492 cached_image_data->RecycleToPlugin(PP_ToBool(init_to_zero));
493 return cached_image_data->GetReference();
496 HostResource result;
497 PP_ImageDataDesc desc;
498 switch (type) {
499 case PPB_ImageData_Shared::SIMPLE: {
500 ppapi::proxy::SerializedHandle image_handle_wrapper;
501 dispatcher->Send(new PpapiHostMsg_PPBImageData_CreateSimple(
502 kApiID, instance, format, size, init_to_zero,
503 &result, &desc, &image_handle_wrapper));
504 if (image_handle_wrapper.is_shmem()) {
505 base::SharedMemoryHandle image_handle = image_handle_wrapper.shmem();
506 if (!result.is_null())
507 return
508 (new SimpleImageData(result, desc, image_handle))->GetReference();
510 break;
512 case PPB_ImageData_Shared::PLATFORM: {
513 #if !defined(OS_NACL)
514 ImageHandle image_handle = PlatformImageData::NullHandle();
515 dispatcher->Send(new PpapiHostMsg_PPBImageData_CreatePlatform(
516 kApiID, instance, format, size, init_to_zero,
517 &result, &desc, &image_handle));
518 if (!result.is_null())
519 return
520 (new PlatformImageData(result, desc, image_handle))->GetReference();
521 #else
522 // PlatformImageData shouldn't be created in untrusted code.
523 NOTREACHED();
524 #endif
525 break;
529 return 0;
532 bool PPB_ImageData_Proxy::OnMessageReceived(const IPC::Message& msg) {
533 bool handled = true;
534 IPC_BEGIN_MESSAGE_MAP(PPB_ImageData_Proxy, msg)
535 #if !defined(OS_NACL)
536 IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBImageData_CreatePlatform,
537 OnHostMsgCreatePlatform)
538 IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBImageData_CreateSimple,
539 OnHostMsgCreateSimple)
540 #endif
541 IPC_MESSAGE_HANDLER(PpapiMsg_PPBImageData_NotifyUnusedImageData,
542 OnPluginMsgNotifyUnusedImageData)
544 IPC_MESSAGE_UNHANDLED(handled = false)
545 IPC_END_MESSAGE_MAP()
546 return handled;
549 #if !defined(OS_NACL)
550 // static
551 PP_Resource PPB_ImageData_Proxy::CreateImageData(
552 PP_Instance instance,
553 PPB_ImageData_Shared::ImageDataType type,
554 PP_ImageDataFormat format,
555 const PP_Size& size,
556 bool init_to_zero,
557 PP_ImageDataDesc* desc,
558 IPC::PlatformFileForTransit* image_handle,
559 uint32_t* byte_count) {
560 HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance);
561 if (!dispatcher)
562 return 0;
564 thunk::EnterResourceCreation enter(instance);
565 if (enter.failed())
566 return 0;
568 PP_Bool pp_init_to_zero = init_to_zero ? PP_TRUE : PP_FALSE;
569 PP_Resource pp_resource = 0;
570 switch (type) {
571 case PPB_ImageData_Shared::SIMPLE: {
572 pp_resource = enter.functions()->CreateImageDataSimple(
573 instance, format, &size, pp_init_to_zero);
574 break;
576 case PPB_ImageData_Shared::PLATFORM: {
577 pp_resource = enter.functions()->CreateImageData(
578 instance, format, &size, pp_init_to_zero);
579 break;
583 if (!pp_resource)
584 return 0;
586 ppapi::ScopedPPResource resource(ppapi::ScopedPPResource::PassRef(),
587 pp_resource);
589 thunk::EnterResourceNoLock<PPB_ImageData_API> enter_resource(resource.get(),
590 false);
591 if (enter_resource.object()->Describe(desc) != PP_TRUE) {
592 DVLOG(1) << "CreateImageData failed: could not Describe";
593 return 0;
596 int local_fd = 0;
597 if (enter_resource.object()->GetSharedMemory(&local_fd,
598 byte_count) != PP_OK) {
599 DVLOG(1) << "CreateImageData failed: could not GetSharedMemory";
600 return 0;
603 #if defined(OS_WIN)
604 *image_handle = dispatcher->ShareHandleWithRemote(
605 reinterpret_cast<HANDLE>(static_cast<intptr_t>(local_fd)), false);
606 #elif defined(OS_POSIX)
607 *image_handle = dispatcher->ShareHandleWithRemote(local_fd, false);
608 #else
609 #error Not implemented.
610 #endif
612 return resource.Release();
615 void PPB_ImageData_Proxy::OnHostMsgCreatePlatform(
616 PP_Instance instance,
617 int32_t format,
618 const PP_Size& size,
619 PP_Bool init_to_zero,
620 HostResource* result,
621 PP_ImageDataDesc* desc,
622 ImageHandle* result_image_handle) {
623 // Clear |desc| so we don't send unitialized memory to the plugin.
624 // https://crbug.com/391023.
625 *desc = PP_ImageDataDesc();
626 IPC::PlatformFileForTransit image_handle;
627 uint32_t byte_count;
628 PP_Resource resource =
629 CreateImageData(instance,
630 PPB_ImageData_Shared::PLATFORM,
631 static_cast<PP_ImageDataFormat>(format),
632 size,
633 true /* init_to_zero */,
634 desc, &image_handle, &byte_count);
635 result->SetHostResource(instance, resource);
636 if (resource) {
637 *result_image_handle = image_handle;
638 } else {
639 *result_image_handle = PlatformImageData::NullHandle();
643 void PPB_ImageData_Proxy::OnHostMsgCreateSimple(
644 PP_Instance instance,
645 int32_t format,
646 const PP_Size& size,
647 PP_Bool init_to_zero,
648 HostResource* result,
649 PP_ImageDataDesc* desc,
650 ppapi::proxy::SerializedHandle* result_image_handle) {
651 // Clear |desc| so we don't send unitialized memory to the plugin.
652 // https://crbug.com/391023.
653 *desc = PP_ImageDataDesc();
654 IPC::PlatformFileForTransit image_handle;
655 uint32_t byte_count;
656 PP_Resource resource =
657 CreateImageData(instance,
658 PPB_ImageData_Shared::SIMPLE,
659 static_cast<PP_ImageDataFormat>(format),
660 size,
661 true /* init_to_zero */,
662 desc, &image_handle, &byte_count);
664 result->SetHostResource(instance, resource);
665 if (resource) {
666 result_image_handle->set_shmem(image_handle, byte_count);
667 } else {
668 result_image_handle->set_null_shmem();
671 #endif // !defined(OS_NACL)
673 void PPB_ImageData_Proxy::OnPluginMsgNotifyUnusedImageData(
674 const HostResource& old_image_data) {
675 PluginGlobals* plugin_globals = PluginGlobals::Get();
676 if (!plugin_globals)
677 return; // This may happen if the plugin is maliciously sending this
678 // message to the renderer.
680 EnterPluginFromHostResource<PPB_ImageData_API> enter(old_image_data);
681 if (enter.succeeded()) {
682 ImageData* image_data = static_cast<ImageData*>(enter.object());
683 ImageDataCache::GetInstance()->ImageDataUsable(image_data);
686 // The renderer sent us a reference with the message. If the image data was
687 // still cached in our process, the proxy still holds a reference so we can
688 // remove the one the renderer just sent is. If the proxy no longer holds a
689 // reference, we released everything and we should also release the one the
690 // renderer just sent us.
691 dispatcher()->Send(new PpapiHostMsg_PPBCore_ReleaseResource(
692 API_ID_PPB_CORE, old_image_data));
695 } // namespace proxy
696 } // namespace ppapi