diff options
author | stuartmorgan@chromium.org <stuartmorgan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-26 18:55:09 +0000 |
---|---|---|
committer | stuartmorgan@chromium.org <stuartmorgan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-03-26 18:55:09 +0000 |
commit | 779b8811261f858eeb19d383ab3647775223c58f (patch) | |
tree | 0c32670d05cf81da511bcafb4075d0596954d8ae /webkit | |
parent | d20dd3d2b4e62d45b1ecae10d4e9cf743d6b21ed (diff) | |
download | chromium_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.h | 22 | ||||
-rw-r--r-- | webkit/glue/plugins/webplugin_delegate_impl_mac.mm | 248 |
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 |