summaryrefslogtreecommitdiffstats
path: root/webkit
diff options
context:
space:
mode:
authorstuartmorgan@chromium.org <stuartmorgan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-26 18:55:09 +0000
committerstuartmorgan@chromium.org <stuartmorgan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-03-26 18:55:09 +0000
commit779b8811261f858eeb19d383ab3647775223c58f (patch)
tree0c32670d05cf81da511bcafb4075d0596954d8ae /webkit
parentd20dd3d2b4e62d45b1ecae10d4e9cf743d6b21ed (diff)
downloadchromium_src-779b8811261f858eeb19d383ab3647775223c58f.zip
chromium_src-779b8811261f858eeb19d383ab3647775223c58f.tar.gz
chromium_src-779b8811261f858eeb19d383ab3647775223c58f.tar.bz2
Add a faster path for QuickDraw plugin drawing for limited use.
The current approcah for QuickDraw plugins--letting them draw into an actual window and then scraping the contents out--is more correct than our original approach, which was to give plugins a port pointing to a pixel buffer instead of the window. Unfortunately, it's also substantially slower to scrape a window than to copy pixel buffers. Most of the time, the old way is good enough for QuickTime, and gives us substantially better framerates. This change makes QuickTime use a hybrid approach, using the fast path when possible, and the slow path when necessary. The primary situations when we need the slow path are: - During event handling - When we need the controls re-painted The are a couple of edge-case event handling regressions, and the time slider seems to move more jerkily, but the improvent in framerate makes it a worthwhile tradeoff (e.g., a large trailer goes from ~25 fps to the full 50). BUG=37968 TEST=Play QuickTime movies; playback should be smoother, and there should be no serious regressions over previous versions. Review URL: http://codereview.chromium.org/1333005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@42794 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit')
-rw-r--r--webkit/glue/plugins/webplugin_delegate_impl.h22
-rw-r--r--webkit/glue/plugins/webplugin_delegate_impl_mac.mm248
2 files changed, 248 insertions, 22 deletions
diff --git a/webkit/glue/plugins/webplugin_delegate_impl.h b/webkit/glue/plugins/webplugin_delegate_impl.h
index 63d90b1..2582d9b 100644
--- a/webkit/glue/plugins/webplugin_delegate_impl.h
+++ b/webkit/glue/plugins/webplugin_delegate_impl.h
@@ -14,6 +14,7 @@
#include "base/file_path.h"
#include "base/ref_counted.h"
#include "base/task.h"
+#include "base/time.h"
#include "base/timer.h"
#include "gfx/native_widget_types.h"
#include "gfx/rect.h"
@@ -66,6 +67,7 @@ class WebPluginDelegateImpl : public webkit_glue::WebPluginDelegate {
PLUGIN_QUIRK_NO_WINDOWLESS = 1024, // Windows
PLUGIN_QUIRK_PATCH_REGENUMKEYEXW = 2048, // Windows
PLUGIN_QUIRK_ALWAYS_NOTIFY_SUCCESS = 4096, // Windows
+ PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH = 8192, // Mac
};
static WebPluginDelegateImpl* Create(const FilePath& filename,
@@ -323,6 +325,11 @@ class WebPluginDelegateImpl : public webkit_glue::WebPluginDelegate {
#endif
#ifndef NP_NO_QUICKDRAW
NP_Port qd_port_;
+ // Variables used for the faster QuickDraw path:
+ GWorldPtr qd_buffer_world_; // Created lazily; may be NULL.
+ GWorldPtr qd_plugin_world_; // Created lazily; may be NULL.
+ bool qd_fast_path_enabled_;
+ base::TimeTicks fast_path_enable_tick_;
#endif
CALayer* layer_; // Used for CA drawing mode. Weak, retained by plug-in.
AcceleratedSurface* surface_;
@@ -395,7 +402,22 @@ class WebPluginDelegateImpl : public webkit_glue::WebPluginDelegate {
#ifndef NP_NO_QUICKDRAW
// Scrapes the contents of our dummy window into the given context.
+ // Used for the slower QuickDraw path.
void ScrapeDummyWindowIntoContext(CGContextRef context);
+
+ // Copies the source GWorld's bits into the target GWorld.
+ // Used for the faster QuickDraw path.
+ void CopyGWorldBits(GWorldPtr source, GWorldPtr dest);
+
+ // Updates the GWorlds used by the faster QuickDraw path.
+ void UpdateGWorlds(CGContextRef context);
+
+ // Sets the mode used for QuickDraw plugin drawing. If enabled is true the
+ // plugin draws into a GWorld that's not connected to a window (the faster
+ // path), otherwise the plugin draws into our invisible dummy window (which is
+ // slower, since the call we use to scrape the window contents is much more
+ // expensive than copying between GWorlds).
+ void SetQuickDrawFastPathEnabled(bool enabled);
#endif
// Adjusts the idle event rate for a Carbon plugin based on its current
diff --git a/webkit/glue/plugins/webplugin_delegate_impl_mac.mm b/webkit/glue/plugins/webplugin_delegate_impl_mac.mm
index 3a6155d..f1f7cbd 100644
--- a/webkit/glue/plugins/webplugin_delegate_impl_mac.mm
+++ b/webkit/glue/plugins/webplugin_delegate_impl_mac.mm
@@ -184,6 +184,11 @@ WebPluginDelegateImpl::WebPluginDelegateImpl(
instance_(instance),
parent_(containing_view),
buffer_context_(NULL),
+#ifndef NP_NO_QUICKDRAW
+ qd_buffer_world_(NULL),
+ qd_plugin_world_(NULL),
+ qd_fast_path_enabled_(false),
+#endif
layer_(nil),
surface_(NULL),
renderer_(nil),
@@ -220,6 +225,10 @@ WebPluginDelegateImpl::~WebPluginDelegateImpl() {
CarbonPluginWindowTracker::SharedInstance()->DestroyDummyWindowForDelegate(
this, reinterpret_cast<WindowRef>(np_cg_context_.window));
}
+#ifndef NP_NO_QUICKDRAW
+ if (quirks_ & PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH)
+ UpdateGWorlds(NULL);
+#endif
#endif
}
@@ -235,6 +244,27 @@ void WebPluginDelegateImpl::PlatformInitialize() {
instance()->mime_type().find("audio/x-pn-realaudio") != std::string::npos)
instance()->plugin_lib()->PreventLibraryUnload();
+#ifndef NP_NO_QUICKDRAW
+ if (instance()->drawing_model() == NPDrawingModelQuickDraw) {
+ // For some QuickDraw plugins, we can sometimes get away with giving them
+ // a port pointing to a pixel buffer instead of a our actual dummy window.
+ // This gives us much better frame rates, because the window scraping we
+ // normally use is very slow.
+ // This breaks down if the plugin does anything complicated with the port
+ // (as QuickTime seems to during event handling, and sometimes when painting
+ // its controls), so we switch on the fly as necessary. (It might be
+ // possible to interpose sufficiently that we wouldn't have to switch back
+ // and forth, but the current approach gets us most of the benefit.)
+ // We can't do this at all with plugins that bypass the port entirely and
+ // attaches their own surface to the window.
+ // TODO(stuartmorgan): Test other QuickDraw plugins that we support and
+ // see if any others can use the fast path.
+ const WebPluginInfo& plugin_info = instance_->plugin_lib()->plugin_info();
+ if (plugin_info.name.find(L"QuickTime") != std::wstring::npos)
+ quirks_ |= PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH;
+ }
+#endif
+
#ifndef NP_NO_CARBON
if (instance()->event_model() == NPEventModelCarbon) {
// Create a stand-in for the browser window so that the plugin will have
@@ -358,12 +388,15 @@ void WebPluginDelegateImpl::Paint(CGContextRef context, const gfx::Rect& rect) {
#ifndef NP_NO_QUICKDRAW
// Paint events are our cue to scrape the dummy window into the real context
+ // (slow path) or copy the offscreen GWorld bits into the context (fast path)
// if we are dealing with a QuickDraw plugin.
- // Note that we use buffer_context_ rather than the Paint parameter
- // because the buffer might have changed during the NPP_HandleEvent call
- // in WindowlessPaint.
+ // Note that we use buffer_context_ rather than |context| because the buffer
+ // might have changed during the NPP_HandleEvent call in WindowlessPaint.
if (instance()->drawing_model() == NPDrawingModelQuickDraw) {
- ScrapeDummyWindowIntoContext(buffer_context_);
+ if (qd_fast_path_enabled_)
+ CopyGWorldBits(qd_plugin_world_, qd_buffer_world_);
+ else
+ ScrapeDummyWindowIntoContext(buffer_context_);
}
#endif
}
@@ -408,18 +441,32 @@ void WebPluginDelegateImpl::WindowlessUpdateGeometry(
clip_rect_ = clip_rect;
bool new_clip_is_empty = clip_rect_.IsEmpty();
+ bool window_rect_changed = (window_rect != window_rect_);
+
// Only resend to the instance if the geometry has changed (see note in
// WindowlessSetWindow for why we only care about the clip rect switching
// empty state).
- if (window_rect == window_rect_ && old_clip_was_empty == new_clip_is_empty)
+ if (!window_rect_changed && old_clip_was_empty == new_clip_is_empty)
return;
- // If visibility has changed, switch our idle event rate.
if (old_clip_was_empty != new_clip_is_empty) {
PluginVisibilityChanged();
}
SetPluginRect(window_rect);
+
+#ifndef NP_NO_QUICKDRAW
+ if (window_rect_changed && qd_fast_path_enabled_) {
+ // Pitch the old GWorlds, since they are the wrong size now; they will be
+ // re-created on demand.
+ UpdateGWorlds(NULL);
+ // If the window size has changed, we need to turn off the fast path so that
+ // the full redraw goes to the window and we get a correct baseline paint.
+ SetQuickDrawFastPathEnabled(false);
+ return; // SetQuickDrawFastPathEnabled will call SetWindow for us.
+ }
+#endif
+
WindowlessSetWindow(true);
}
@@ -456,6 +503,15 @@ void WebPluginDelegateImpl::WindowlessPaint(gfx::NativeDrawingContext context,
ScopedActiveDelegate active_delegate(this);
+#ifndef NP_NO_QUICKDRAW
+ if (instance()->drawing_model() == NPDrawingModelQuickDraw) {
+ if (qd_fast_path_enabled_)
+ SetGWorld(qd_port_.port, NULL);
+ else
+ SetPort(qd_port_.port);
+ }
+#endif
+
CGContextSaveGState(context);
switch (instance()->event_model()) {
@@ -517,6 +573,16 @@ void WebPluginDelegateImpl::WindowlessSetWindow(bool force_set_window) {
SetWindowHasFocus(initial_window_focus_);
}
+#ifndef NP_NO_QUICKDRAW
+ if ((quirks_ & PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH) &&
+ !qd_fast_path_enabled_ && clip_rect_.IsEmpty()) {
+ // Give the plugin a few seconds to stabilize so we get a good initial paint
+ // to use as a baseline, then switch to the fast path.
+ fast_path_enable_tick_ = base::TimeTicks::Now() +
+ base::TimeDelta::FromSeconds(3);
+ }
+#endif
+
DCHECK(err == NPERR_NO_ERROR);
}
@@ -579,6 +645,12 @@ void WebPluginDelegateImpl::SetWindowHasFocus(bool has_focus) {
return;
containing_window_has_focus_ = has_focus;
+#ifndef NP_NO_QUICKDRAW
+ // Make sure controls repaint with the correct look.
+ if (qd_fast_path_enabled_)
+ SetQuickDrawFastPathEnabled(false);
+#endif
+
ScopedActiveDelegate active_delegate(this);
switch (instance()->event_model()) {
#ifndef NP_NO_CARBON
@@ -764,6 +836,96 @@ void WebPluginDelegateImpl::ScrapeDummyWindowIntoContext(CGContextRef context) {
window_id, 0);
CGContextRestoreGState(context);
}
+
+void WebPluginDelegateImpl::CopyGWorldBits(GWorldPtr source, GWorldPtr dest) {
+ if (!(source && dest))
+ return;
+
+ Rect window_bounds = { 0, 0, window_rect_.height(), window_rect_.width() };
+ PixMapHandle source_pixmap = GetGWorldPixMap(source);
+ if (LockPixels(source_pixmap)) {
+ PixMapHandle dest_pixmap = GetGWorldPixMap(dest);
+ if (LockPixels(dest_pixmap)) {
+ SetGWorld(qd_buffer_world_, NULL);
+ // Set foreground and background colors to avoid "colorizing" the image.
+ ForeColor(blackColor);
+ BackColor(whiteColor);
+ CopyBits(reinterpret_cast<BitMap*>(*source_pixmap),
+ reinterpret_cast<BitMap*>(*dest_pixmap),
+ &window_bounds, &window_bounds, srcCopy, NULL);
+ UnlockPixels(dest_pixmap);
+ }
+ UnlockPixels(source_pixmap);
+ }
+}
+
+void WebPluginDelegateImpl::UpdateGWorlds(CGContextRef context) {
+ if (qd_plugin_world_) {
+ DisposeGWorld(qd_plugin_world_);
+ qd_plugin_world_ = NULL;
+ }
+ if (qd_buffer_world_) {
+ DisposeGWorld(qd_buffer_world_);
+ qd_buffer_world_ = NULL;
+ }
+ if (!context)
+ return;
+
+ gfx::Size dimensions = window_rect_.size();
+ Rect window_bounds = {
+ 0, 0, dimensions.height(), dimensions.width()
+ };
+ // Create a GWorld pointing at the same bits as our buffer context.
+ if (context) {
+ NewGWorldFromPtr(
+ &qd_buffer_world_, k32BGRAPixelFormat, &window_bounds,
+ NULL, NULL, 0, static_cast<Ptr>(CGBitmapContextGetData(context)),
+ static_cast<SInt32>(CGBitmapContextGetBytesPerRow(context)));
+ }
+ // Create a GWorld for the plugin to paint into whenever it wants.
+ NewGWorld(&qd_plugin_world_, k32ARGBPixelFormat, &window_bounds,
+ NULL, NULL, kNativeEndianPixMap);
+ if (qd_fast_path_enabled_)
+ qd_port_.port = qd_plugin_world_;
+}
+
+void WebPluginDelegateImpl::SetQuickDrawFastPathEnabled(bool enabled) {
+ if (!enabled) {
+ // Wait a couple of seconds, then turn the fast path back on. If we're
+ // turning it off for event handling, that ensures that the common case of
+ // move-mouse-then-click works (as well as making it likely that a second
+ // click attempt will work if the first one fails). If we're turning it
+ // off to force a new baseline image, this leaves plenty of time for the
+ // plugin to draw.
+ fast_path_enable_tick_ = base::TimeTicks::Now() +
+ base::TimeDelta::FromSeconds(2);
+ }
+
+ if (enabled == qd_fast_path_enabled_)
+ return;
+ if (enabled && clip_rect_.IsEmpty()) {
+ // Don't switch to the fast path while the plugin is completely clipped;
+ // we can only switch when the window has an up-to-date image for us to
+ // scrape. We'll automatically switch after we become visible again.
+ return;
+ }
+
+ qd_fast_path_enabled_ = enabled;
+ if (enabled) {
+ if (!qd_plugin_world_)
+ UpdateGWorlds(buffer_context_);
+ qd_port_.port = qd_plugin_world_;
+ // Copy our last window snapshot into our new source, since the plugin
+ // may not repaint everything.
+ CopyGWorldBits(qd_buffer_world_, qd_plugin_world_);
+ } else {
+ qd_port_.port =
+ GetWindowPort(reinterpret_cast<WindowRef>(np_cg_context_.window));
+ }
+ WindowlessSetWindow(true);
+ // Send a paint event so that the new buffer gets updated immediately.
+ WindowlessPaint(buffer_context_, clip_rect_);
+}
#endif // !NP_NO_QUICKDRAW
void WebPluginDelegateImpl::UpdateIdleEventRate() {
@@ -1154,6 +1316,44 @@ bool WebPluginDelegateImpl::PlatformHandleInputEvent(
current_windowless_cursor_.GetCursorInfo(cursor_info);
}
+#ifndef NP_NO_CARBON
+ if (instance()->event_model() == NPEventModelCarbon) {
+#ifndef NP_NO_QUICKDRAW
+ if (instance()->drawing_model() == NPDrawingModelQuickDraw) {
+ if (quirks_ & PLUGIN_QUIRK_ALLOW_FASTER_QUICKDRAW_PATH) {
+ // Mouse event handling doesn't work correctly in the fast path mode,
+ // so any time we get a mouse event turn the fast path off, but set a
+ // time to switch it on again (we don't rely just on MouseLeave because
+ // we don't want poor performance in the case of clicking the play
+ // button and then leaving the mouse there).
+ // This isn't perfect (specifically, click-and-hold doesn't seem to work
+ // if the fast path is on), but the slight regression is worthwhile
+ // for the improved framerates.
+ if (WebInputEventIsWebMouseEvent(event)) {
+ if (event.type == WebInputEvent::MouseLeave) {
+ SetQuickDrawFastPathEnabled(true);
+ } else {
+ SetQuickDrawFastPathEnabled(false);
+ }
+ // Make sure the plugin wasn't destroyed during the switch.
+ if (!instance())
+ return false;
+ }
+ }
+
+ if (qd_fast_path_enabled_)
+ SetGWorld(qd_port_.port, NULL);
+ else
+ SetPort(qd_port_.port);
+ }
+#endif
+
+ if (event.type == WebInputEvent::MouseMove) {
+ return true; // The recurring FireIdleEvent will send null events.
+ }
+ }
+#endif
+
// if we do not currently have focus and this is a mouseDown, trigger a
// notification that we are taking the keyboard focus. We can't just key
// off of incoming calls to SetFocus, since WebKit may already think we
@@ -1165,20 +1365,6 @@ bool WebPluginDelegateImpl::PlatformHandleInputEvent(
return false;
}
-#ifndef NP_NO_CARBON
- if (instance()->event_model() == NPEventModelCarbon) {
- if (event.type == WebInputEvent::MouseMove) {
- return true; // The recurring OnNull will send null events.
- }
-
-#ifndef NP_NO_QUICKDRAW
- if (instance()->drawing_model() == NPDrawingModelQuickDraw) {
- SetPort(qd_port_.port);
- }
-#endif
- }
-#endif
-
ScopedActiveDelegate active_delegate(this);
// Create the plugin event structure, and send it to the plugin.
@@ -1238,8 +1424,26 @@ void WebPluginDelegateImpl::FireIdleEvent() {
if (!instance())
return;
+#ifndef NP_NO_QUICKDRAW
+ // Check whether it's time to turn the QuickDraw fast path back on.
+ if (!fast_path_enable_tick_.is_null() &&
+ (base::TimeTicks::Now() > fast_path_enable_tick_)) {
+ SetQuickDrawFastPathEnabled(true);
+ fast_path_enable_tick_ = base::TimeTicks();
+ }
+#endif
+
ScopedActiveDelegate active_delegate(this);
+#ifndef NP_NO_QUICKDRAW
+ if (instance()->drawing_model() == NPDrawingModelQuickDraw) {
+ if (qd_fast_path_enabled_)
+ SetGWorld(qd_port_.port, NULL);
+ else
+ SetPort(qd_port_.port);
+ }
+#endif
+
// Send an idle event so that the plugin can do background work
NPEvent np_event = {0};
np_event.what = nullEvent;
@@ -1256,8 +1460,8 @@ void WebPluginDelegateImpl::FireIdleEvent() {
#ifndef NP_NO_QUICKDRAW
// Quickdraw-based plugins can draw at any time, so tell the renderer to
// repaint.
- // TODO: only do this if the contents of the offscreen window has changed,
- // so as not to spam the renderer with an unchanging image.
+ // TODO: only do this if the contents of the offscreen window/buffer have
+ // changed, so as not to spam the renderer with an unchanging image.
if (instance() && instance()->drawing_model() == NPDrawingModelQuickDraw)
instance()->webplugin()->Invalidate();
#endif