Chromium Code Reviews| Index: ppapi/proxy/ppb_image_data_proxy.cc |
| diff --git a/ppapi/proxy/ppb_image_data_proxy.cc b/ppapi/proxy/ppb_image_data_proxy.cc |
| index 2eecc0ffe19972c0a1d4921f2754dda2df14e40c..12715d861c5cbbea54cbd57a84fdba2ccf0ddb2a 100644 |
| --- a/ppapi/proxy/ppb_image_data_proxy.cc |
| +++ b/ppapi/proxy/ppb_image_data_proxy.cc |
| @@ -6,15 +6,20 @@ |
| #include <string.h> // For memcpy |
| +#include <map> |
| #include <vector> |
| #include "base/logging.h" |
| +#include "base/memory/singleton.h" |
| +#include "base/memory/weak_ptr.h" |
| #include "build/build_config.h" |
| #include "ppapi/c/pp_completion_callback.h" |
| #include "ppapi/c/pp_errors.h" |
| #include "ppapi/c/pp_resource.h" |
| +#include "ppapi/proxy/enter_proxy.h" |
| #include "ppapi/proxy/host_dispatcher.h" |
| #include "ppapi/proxy/plugin_dispatcher.h" |
| +#include "ppapi/proxy/plugin_globals.h" |
| #include "ppapi/proxy/plugin_resource_tracker.h" |
| #include "ppapi/proxy/ppapi_messages.h" |
| #include "ppapi/shared_impl/host_resource.h" |
| @@ -27,9 +32,253 @@ |
| #include "ui/surface/transport_dib.h" |
| #endif |
| +using ppapi::thunk::PPB_ImageData_API; |
| + |
| namespace ppapi { |
| namespace proxy { |
| +namespace { |
| + |
| +// How ImageData re-use works |
| +// -------------------------- |
| +// |
| +// When a plugin does ReplaceContents, it transfers the ImageData to the system |
| +// for use as the backing store for the instance. When animating plugins (like |
| +// video) re-creating image datas for each frame and mapping the memory has a |
| +// high overhead. So we try to re-use these when possible. |
| +// |
| +// 1. Plugin does ReplaceContents and Flush and the proxy queues up an |
| +// asynchronous request to the renderer. |
| +// 2. Plugin frees its ImageData reference. If it doesn't do this we can't |
| +// re-use it. |
| +// 3. When the last plugin ref of an ImageData is released, we don't actually |
| +// delete it. Instead we put it on a queue where we hold onto it in the |
| +// plugin process for a short period of time. |
| +// 4. When the Flush for the Graphics2D.ReplaceContents is executed, the proxy |
| +// will request the old ImageData. This is the one that's being replaced by |
| +// the new contents so is being abandoned, and without our caching system it |
| +// would get deleted at this point. |
| +// 5. The proxy in the renderer will send NotifyUnusedImageData back to the |
| +// plugin process. We check if the given resource is in the queue and mark |
| +// is as usable. |
|
yzshen1
2012/08/21 22:07:24
is -> it.
|
| +// 6. When the plugin requests a new image data, we check our queue and if there |
| +// is a usable ImageData of the right size and format, we'll return it |
| +// instead of making a new one. Since when you're doing full frame |
| +// animations, generally the size doesn't change so cache hits should be |
| +// high. |
| +// |
| +// Some notes: |
| +// |
| +// - We only re-use image datas when the plugin does ReplaceContents on them. |
| +// Theoretically we could re-use them in other cases but the lifetime |
| +// becomes more difficult to manage. The plugin could have used an ImageData |
| +// in an arbitrary number of queued up PaintImageData calls which we would |
| +// have to check. By doing ReplaceContents, the plugin is promising that it's |
| +// done with the image, so this is a good signal. |
| +// |
| +// - If a flush takes a long time or there are many released image datas |
| +// accumulating in our queue such that some are deleted, we will have |
| +// released our reference by the time the renderer notifies us of an unused |
| +// image data. In this case we just give up. |
| +// |
| +// - We maintain a per-instance cache. Some pages have many instances of |
| +// Flash, for example, each of a different size. If they're all animating we |
| +// want each to get its own image data re-use. |
| +// |
| +// - We generate new resource IDs when re-use happens to try to avoid weird |
| +// problems if the plugin messes up its refcounting. |
| + |
| +// Keep a cache entry for this many seconds before expiring it. We get an entry |
| +// back from the renderer after an ImageData is swapped out, so it means the |
| +// plugin has to be painting at at least two frames for this time interfacl to |
|
yzshen1
2012/08/21 22:07:24
interfacl -> interval
at at -> at
|
| +// get caching. |
| +static const int kMaxAgeSeconds = 2; |
| + |
| +// ImageDataCacheEntry --------------------------------------------------------- |
| + |
| +struct ImageDataCacheEntry { |
| + ImageDataCacheEntry() : added_time(), usable(false), image() {} |
| + ImageDataCacheEntry(ImageData* i) |
| + : added_time(base::TimeTicks::Now()), |
| + usable(false), |
| + image(i) { |
| + } |
| + |
| + base::TimeTicks added_time; |
| + |
| + // Set to true when the renderer tells us that it's OK to re-use this iamge. |
| + bool usable; |
| + |
| + scoped_refptr<ImageData> image; |
| +}; |
| + |
| +// ImageDataInstanceCache ------------------------------------------------------ |
| + |
| +// Per-instance cache of image datas. |
| +class ImageDataInstanceCache { |
| + public: |
| + ImageDataInstanceCache() : next_insertion_point_(0) {} |
| + |
| + // These functions have the same spec as the ones in ImageDataCache. |
| + scoped_refptr<ImageData> Get(int width, int height, |
| + PP_ImageDataFormat format); |
| + void Add(ImageData* image_data); |
| + void ImageDataUsable(ImageData* image_data); |
| + |
| + // Expires old entries. Returns true if there are still entries in the list, |
| + // false if this instance cache is now empty. |
| + bool ExpireEntries(); |
| + |
| + private: |
| + // We'll store this many ImageDatas per instance. |
| + const static int kCacheSize = 2; |
| + |
| + ImageDataCacheEntry images_[kCacheSize]; |
| + |
| + // Index into cache where the next item will go. |
| + int next_insertion_point_; |
| +}; |
| + |
| +scoped_refptr<ImageData> ImageDataInstanceCache::Get( |
| + int width, int height, |
| + PP_ImageDataFormat format) { |
| + // Just do a brute-force search since the cache is so small. |
| + for (int i = 0; i < kCacheSize; i++) { |
| + if (!images_[i].usable) |
| + continue; |
| + const PP_ImageDataDesc& desc = images_[i].image->desc(); |
| + if (desc.format == format && |
| + desc.size.width == width && desc.size.height == height) { |
| + scoped_refptr<ImageData> ret(images_[i].image); |
| + images_[i] = ImageDataCacheEntry(); |
| + return ret; |
| + } |
| + } |
| + return scoped_refptr<ImageData>(); |
| +} |
| + |
| +void ImageDataInstanceCache::Add(ImageData* image_data) { |
| + images_[next_insertion_point_] = ImageDataCacheEntry(image_data); |
| + |
| + // Go to the next location, wrapping around to get LRU. |
| + next_insertion_point_++; |
| + if (next_insertion_point_ >= kCacheSize) |
| + next_insertion_point_ = 0; |
| +} |
| + |
| +void ImageDataInstanceCache::ImageDataUsable(ImageData* image_data) { |
| + for (int i = 0; i < kCacheSize; i++) { |
| + if (images_[i].image.get() == image_data) { |
| + images_[i].usable = true; |
| + return; |
| + } |
| + } |
| +} |
| + |
| +bool ImageDataInstanceCache::ExpireEntries() { |
| + base::TimeTicks threshold_time = |
| + base::TimeTicks::Now() - base::TimeDelta::FromSeconds(kMaxAgeSeconds); |
| + |
| + bool has_entry = false; |
| + for (int i = 0; i < kCacheSize; i++) { |
| + if (images_[i].image.get()) { |
| + // Entry present. |
| + if (images_[i].added_time <= threshold_time) { |
| + // Found an entry to expire. |
| + images_[i] = ImageDataCacheEntry(); |
| + next_insertion_point_ = i; |
| + } else { |
| + // Found an entry that we're keeping. |
| + has_entry = true; |
| + } |
| + } |
| + } |
| + return has_entry; |
| +} |
| + |
| +// ImageDataCache -------------------------------------------------------------- |
| + |
| +class ImageDataCache { |
| + public: |
| + ImageDataCache() : weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {} |
| + ~ImageDataCache() {} |
| + |
| + static ImageDataCache* GetInstance(); |
| + |
| + // Retrieves an image data from the cache of the specified size and format if |
| + // one exists. If one doesn't exist, this will return a null refptr. |
| + scoped_refptr<ImageData> Get(PP_Instance instance, |
| + int width, int height, |
| + PP_ImageDataFormat format); |
| + |
| + // Adds the given image data to the cache. There should be no plugin |
| + // references to it. This may delete an older item from the cache. |
| + void Add(ImageData* image_data); |
| + |
| + // Notification from the renderer that the given image data is usable. |
| + void ImageDataUsable(ImageData* image_data); |
| + |
| + private: |
| + friend struct LeakySingletonTraits<ImageDataCache>; |
| + |
| + // Timer callback to expire entries for the given instance. |
| + void OnTimer(PP_Instance instance); |
| + |
| + base::WeakPtrFactory<ImageDataCache> weak_factory_; |
|
yzshen1
2012/08/21 22:07:24
Why does it need to be a weak ptr factory?
brettw
2012/08/21 23:24:58
I added a comment
|
| + |
| + typedef std::map<PP_Instance, ImageDataInstanceCache> CacheMap; |
| + CacheMap cache_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ImageDataCache); |
| +}; |
| + |
| +// static |
| +ImageDataCache* ImageDataCache::GetInstance() { |
| + return Singleton<ImageDataCache, |
| + LeakySingletonTraits<ImageDataCache> >::get(); |
| +} |
| + |
| +scoped_refptr<ImageData> ImageDataCache::Get(PP_Instance instance, |
| + int width, int height, |
| + PP_ImageDataFormat format) { |
| + CacheMap::iterator found = cache_.find(instance); |
| + if (found == cache_.end()) |
| + return scoped_refptr<ImageData>(); |
| + return found->second.Get(width, height, format); |
| +} |
| + |
| +void ImageDataCache::Add(ImageData* image_data) { |
| + cache_[image_data->pp_instance()].Add(image_data); |
| + |
| + // Schedule a timer to invalidate this entry. |
| + MessageLoop::current()->PostDelayedTask( |
| + FROM_HERE, |
| + base::Bind(&ImageDataCache::OnTimer, |
| + weak_factory_.GetWeakPtr(), |
| + image_data->pp_instance()), |
| + base::TimeDelta::FromSeconds(kMaxAgeSeconds)); |
| +} |
| + |
| +void ImageDataCache::ImageDataUsable(ImageData* image_data) { |
| + CacheMap::iterator found = cache_.find(image_data->pp_instance()); |
| + if (found != cache_.end()) |
| + found->second.ImageDataUsable(image_data); |
| +} |
| + |
| +void ImageDataCache::OnTimer(PP_Instance instance) { |
| + CacheMap::iterator found = cache_.find(instance); |
| + if (found == cache_.end()) |
| + return; |
| + if (!found->second.ExpireEntries()) { |
| + // There are no more entries for this instance, remove it from the cache. |
| + cache_.erase(found); |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +// ImageData ------------------------------------------------------------------- |
| + |
| #if !defined(OS_NACL) |
| ImageData::ImageData(const HostResource& resource, |
| const PP_ImageDataDesc& desc, |
| @@ -58,10 +307,15 @@ ImageData::ImageData(const HostResource& resource, |
| ImageData::~ImageData() { |
| } |
| -thunk::PPB_ImageData_API* ImageData::AsPPB_ImageData_API() { |
| +PPB_ImageData_API* ImageData::AsPPB_ImageData_API() { |
| return this; |
| } |
| +void ImageData::LastPluginRefWasDeleted() { |
| + // The plugin no longer needs this ImageData, add it to our cache. |
| + ImageDataCache::GetInstance()->Add(this); |
| +} |
| + |
| PP_Bool ImageData::Describe(PP_ImageDataDesc* desc) { |
| memcpy(desc, &desc_, sizeof(PP_ImageDataDesc)); |
| return PP_TRUE; |
| @@ -121,6 +375,12 @@ SkCanvas* ImageData::GetCanvas() { |
| #endif |
| } |
| +void ImageData::ZeroContents() { |
| + void* data = Map(); |
| + memset(data, 0, desc_.stride * desc_.size.height); |
| + Unmap(); |
| +} |
| + |
| #if !defined(OS_NACL) |
| // static |
| ImageHandle ImageData::NullHandle() { |
| @@ -144,6 +404,8 @@ ImageHandle ImageData::HandleFromInt(int32_t i) { |
| } |
| #endif // !defined(OS_NACL) |
| +// PPB_ImageData_Proxy --------------------------------------------------------- |
| + |
| PPB_ImageData_Proxy::PPB_ImageData_Proxy(Dispatcher* dispatcher) |
| : InterfaceProxy(dispatcher) { |
| } |
| @@ -160,6 +422,18 @@ PP_Resource PPB_ImageData_Proxy::CreateProxyResource(PP_Instance instance, |
| if (!dispatcher) |
| return 0; |
| + // Check the cache. |
| + scoped_refptr<ImageData> cached_image_data = |
| + ImageDataCache::GetInstance()->Get(instance, size.width, size.height, |
| + format); |
| + if (cached_image_data.get()) { |
| + // We have one we can re-use rather than allocating a new one. Only zero |
| + // out if requested. |
| + if (PP_ToBool(init_to_zero)) |
| + cached_image_data->ZeroContents(); |
| + return cached_image_data->GetReference(); |
| + } |
| + |
| HostResource result; |
| std::string image_data_desc; |
| #if defined(OS_NACL) |
| @@ -190,6 +464,10 @@ bool PPB_ImageData_Proxy::OnMessageReceived(const IPC::Message& msg) { |
| IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBImageData_Create, OnHostMsgCreate) |
| IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBImageData_CreateNaCl, |
| OnHostMsgCreateNaCl) |
| + |
| + IPC_MESSAGE_HANDLER(PpapiPluginMsg_PPBImageData_NotifyUnusedImageData, |
| + OnPluginMsgNotifyUnusedImageData) |
| + |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| return handled; |
| @@ -220,8 +498,7 @@ void PPB_ImageData_Proxy::OnHostMsgCreate(PP_Instance instance, |
| result->SetHostResource(instance, resource); |
| // Get the description, it's just serialized as a string. |
| - thunk::EnterResourceNoLock<thunk::PPB_ImageData_API> enter_resource( |
| - resource, false); |
| + thunk::EnterResourceNoLock<PPB_ImageData_API> enter_resource(resource, false); |
| PP_ImageDataDesc desc; |
| if (enter_resource.object()->Describe(&desc) == PP_TRUE) { |
| image_data_desc->resize(sizeof(PP_ImageDataDesc)); |
| @@ -271,8 +548,7 @@ void PPB_ImageData_Proxy::OnHostMsgCreateNaCl( |
| result->SetHostResource(instance, resource); |
| // Get the description, it's just serialized as a string. |
| - thunk::EnterResourceNoLock<thunk::PPB_ImageData_API> enter_resource( |
| - resource, false); |
| + thunk::EnterResourceNoLock<PPB_ImageData_API> enter_resource(resource, false); |
| if (enter_resource.failed()) |
| return; |
| PP_ImageDataDesc desc; |
| @@ -300,5 +576,27 @@ void PPB_ImageData_Proxy::OnHostMsgCreateNaCl( |
| #endif // defined(OS_NACL) |
| } |
| +void PPB_ImageData_Proxy::OnPluginMsgNotifyUnusedImageData( |
| + const HostResource& old_image_data) { |
| + PluginGlobals* plugin_globals = PluginGlobals::Get(); |
| + if (!plugin_globals) |
| + return; // This may happen if the plugin is malixiously sending this |
|
yzshen1
2012/08/21 22:07:24
malixiously -> maliciously
|
| + // message to the renderer. |
| + |
| + EnterPluginFromHostResource<PPB_ImageData_API> enter(old_image_data); |
| + if (enter.succeeded()) { |
| + ImageData* image_data = static_cast<ImageData*>(enter.object()); |
| + ImageDataCache::GetInstance()->ImageDataUsable(image_data); |
| + } |
| + |
| + // The renderer sent us a reference with the message. If the image data was |
| + // still cached in our process, the proxy still holds a reference so we can |
| + // remove the one the renderer just sent is. If the proxy no longer holds a |
| + // reference, we released everything and we should also release the one the |
| + // renderer just sent us. |
| + dispatcher()->Send(new PpapiHostMsg_PPBCore_ReleaseResource( |
| + API_ID_PPB_CORE, old_image_data)); |
| +} |
| + |
| } // namespace proxy |
| } // namespace ppapi |