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
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"
33 #include "skia/ext/platform_canvas.h"
34 #include "ui/surface/transport_dib.h"
37 using ppapi::thunk::PPB_ImageData_API
;
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
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
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
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.
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
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
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()),
107 base::TimeTicks added_time
;
109 // Set to true when the renderer tells us that it's OK to re-use this iamge.
112 scoped_refptr
<ImageData
> image
;
115 // ImageDataInstanceCache ------------------------------------------------------
117 // Per-instance cache of image datas.
118 class ImageDataInstanceCache
{
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();
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
)
153 if (images_
[i
].image
->type() != type
)
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
163 next_insertion_point_
= i
;
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();
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()) {
201 if (images_
[i
].added_time
<= threshold_time
) {
202 // Found an entry to expire.
203 images_
[i
] = ImageDataCacheEntry();
204 next_insertion_point_
= i
;
206 // Found an entry that we're keeping.
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
{
225 ImageDataCache() : weak_factory_(this) {}
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
);
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
;
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
);
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 PpapiGlobals::Get()->GetMainThreadMessageLoop()->PostDelayedTask(
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())
307 if (!found
->second
.ExpireEntries()) {
308 // There are no more entries for this instance, remove it from the cache.
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
),
323 is_candidate_for_reuse_(false) {
326 ImageData::~ImageData() {
329 PPB_ImageData_API
* ImageData::AsPPB_ImageData_API() {
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
));
350 int32_t ImageData::GetSharedMemory(base::SharedMemory
** /* shm */,
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;
365 memset(data
, 0, desc_
.stride
* desc_
.size
.height
);
370 // PlatformImageData -----------------------------------------------------------
372 #if !defined(OS_NACL)
373 PlatformImageData::PlatformImageData(const HostResource
& resource
,
374 const PP_ImageDataDesc
& desc
,
376 : ImageData(resource
, PPB_ImageData_Shared::PLATFORM
, desc
) {
378 transport_dib_
.reset(TransportDIB::CreateWithHandle(handle
));
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
,
391 if (!mapped_canvas_
.get())
394 const SkBitmap
& bitmap
=
395 skia::GetTopDevice(*mapped_canvas_
)->accessBitmap(true);
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();
416 ImageHandle
PlatformImageData::NullHandle() {
420 return ImageHandle();
424 ImageHandle
PlatformImageData::HandleFromInt(int32_t i
) {
426 return reinterpret_cast<ImageHandle
>(i
);
428 return ImageHandle(i
, false);
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),
444 SimpleImageData::~SimpleImageData() {
447 void* SimpleImageData::Map() {
448 if (map_count_
++ == 0)
450 return shm_
.memory();
453 void SimpleImageData::Unmap() {
454 if (--map_count_
== 0)
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() {
476 PP_Resource
PPB_ImageData_Proxy::CreateProxyResource(
477 PP_Instance instance
,
478 PPB_ImageData_Shared::ImageDataType type
,
479 PP_ImageDataFormat format
,
481 PP_Bool init_to_zero
) {
482 PluginDispatcher
* dispatcher
= PluginDispatcher::GetForInstance(instance
);
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();
497 PP_ImageDataDesc desc
;
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())
508 (new SimpleImageData(result
, desc
, image_handle
))->GetReference();
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())
520 (new PlatformImageData(result
, desc
, image_handle
))->GetReference();
522 // PlatformImageData shouldn't be created in untrusted code.
532 bool PPB_ImageData_Proxy::OnMessageReceived(const IPC::Message
& msg
) {
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
)
541 IPC_MESSAGE_HANDLER(PpapiMsg_PPBImageData_NotifyUnusedImageData
,
542 OnPluginMsgNotifyUnusedImageData
)
544 IPC_MESSAGE_UNHANDLED(handled
= false)
545 IPC_END_MESSAGE_MAP()
549 #if !defined(OS_NACL)
551 PP_Resource
PPB_ImageData_Proxy::CreateImageData(
552 PP_Instance instance
,
553 PPB_ImageData_Shared::ImageDataType type
,
554 PP_ImageDataFormat format
,
557 PP_ImageDataDesc
* desc
,
558 base::SharedMemoryHandle
* image_handle
,
559 uint32_t* byte_count
) {
560 HostDispatcher
* dispatcher
= HostDispatcher::GetForInstance(instance
);
564 thunk::EnterResourceCreation
enter(instance
);
568 PP_Bool pp_init_to_zero
= init_to_zero
? PP_TRUE
: PP_FALSE
;
569 PP_Resource pp_resource
= 0;
571 case PPB_ImageData_Shared::SIMPLE
: {
572 pp_resource
= enter
.functions()->CreateImageDataSimple(
573 instance
, format
, &size
, pp_init_to_zero
);
576 case PPB_ImageData_Shared::PLATFORM
: {
577 pp_resource
= enter
.functions()->CreateImageData(
578 instance
, format
, &size
, pp_init_to_zero
);
586 ppapi::ScopedPPResource
resource(ppapi::ScopedPPResource::PassRef(),
589 thunk::EnterResourceNoLock
<PPB_ImageData_API
> enter_resource(resource
.get(),
591 if (enter_resource
.object()->Describe(desc
) != PP_TRUE
) {
592 DVLOG(1) << "CreateImageData failed: could not Describe";
596 base::SharedMemory
* local_shm
;
597 if (enter_resource
.object()->GetSharedMemory(&local_shm
, byte_count
) !=
599 DVLOG(1) << "CreateImageData failed: could not GetSharedMemory";
604 dispatcher
->ShareSharedMemoryHandleWithRemote(local_shm
->handle());
605 return resource
.Release();
608 void PPB_ImageData_Proxy::OnHostMsgCreatePlatform(
609 PP_Instance instance
,
612 PP_Bool init_to_zero
,
613 HostResource
* result
,
614 PP_ImageDataDesc
* desc
,
615 ImageHandle
* result_image_handle
) {
616 // Clear |desc| so we don't send unitialized memory to the plugin.
617 // https://crbug.com/391023.
618 *desc
= PP_ImageDataDesc();
619 base::SharedMemoryHandle image_handle
;
621 PP_Resource resource
=
622 CreateImageData(instance
,
623 PPB_ImageData_Shared::PLATFORM
,
624 static_cast<PP_ImageDataFormat
>(format
),
626 true /* init_to_zero */,
627 desc
, &image_handle
, &byte_count
);
628 result
->SetHostResource(instance
, resource
);
630 *result_image_handle
= image_handle
;
632 *result_image_handle
= PlatformImageData::NullHandle();
636 void PPB_ImageData_Proxy::OnHostMsgCreateSimple(
637 PP_Instance instance
,
640 PP_Bool init_to_zero
,
641 HostResource
* result
,
642 PP_ImageDataDesc
* desc
,
643 ppapi::proxy::SerializedHandle
* result_image_handle
) {
644 // Clear |desc| so we don't send unitialized memory to the plugin.
645 // https://crbug.com/391023.
646 *desc
= PP_ImageDataDesc();
647 base::SharedMemoryHandle image_handle
;
649 PP_Resource resource
=
650 CreateImageData(instance
,
651 PPB_ImageData_Shared::SIMPLE
,
652 static_cast<PP_ImageDataFormat
>(format
),
654 true /* init_to_zero */,
655 desc
, &image_handle
, &byte_count
);
657 result
->SetHostResource(instance
, resource
);
659 result_image_handle
->set_shmem(image_handle
, byte_count
);
661 result_image_handle
->set_null_shmem();
664 #endif // !defined(OS_NACL)
666 void PPB_ImageData_Proxy::OnPluginMsgNotifyUnusedImageData(
667 const HostResource
& old_image_data
) {
668 PluginGlobals
* plugin_globals
= PluginGlobals::Get();
670 return; // This may happen if the plugin is maliciously sending this
671 // message to the renderer.
673 EnterPluginFromHostResource
<PPB_ImageData_API
> enter(old_image_data
);
674 if (enter
.succeeded()) {
675 ImageData
* image_data
= static_cast<ImageData
*>(enter
.object());
676 ImageDataCache::GetInstance()->ImageDataUsable(image_data
);
679 // The renderer sent us a reference with the message. If the image data was
680 // still cached in our process, the proxy still holds a reference so we can
681 // remove the one the renderer just sent is. If the proxy no longer holds a
682 // reference, we released everything and we should also release the one the
683 // renderer just sent us.
684 dispatcher()->Send(new PpapiHostMsg_PPBCore_ReleaseResource(
685 API_ID_PPB_CORE
, old_image_data
));