Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(614)

Unified Diff: ppapi/proxy/ppb_image_data_proxy.cc

Issue 10823378: Cache image data objects. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698