Calling OpenGL from the renderer process
- Added ability for renderer processes to render to a real window (Windows only so far).
- Added ability to create offscreen frame buffer objects that can be resized later.
- OpenGL context can have a "parent" context that can access its last swapped back buffer through a texture ID.
- Moved code to establish GPU channel from RenderWidget to RenderThread.
- Changed way service size command buffer object lifetimes are managed.
TEST=trybot and visual verification that OpenGL can clear the browser window to magenta.
BUG=none

Review URL: http://codereview.chromium.org/1136006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@42679 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/gpu_process_host.cc b/chrome/browser/gpu_process_host.cc
index fc10107..9fa00925 100644
--- a/chrome/browser/gpu_process_host.cc
+++ b/chrome/browser/gpu_process_host.cc
@@ -126,13 +126,11 @@
   router_.RemoveRoute(routing_id);
 }
 
-void GpuProcessHost::EstablishGpuChannel(
-    int renderer_id,
-    int routing_id) {
+void GpuProcessHost::EstablishGpuChannel(int renderer_id) {
   if (Send(new GpuMsg_EstablishChannel(renderer_id)))
-    sent_requests_.push(ChannelRequest(renderer_id, routing_id));
+    sent_requests_.push(ChannelRequest(renderer_id));
   else
-    ReplyToRenderer(renderer_id, routing_id, IPC::ChannelHandle());
+    ReplyToRenderer(renderer_id, IPC::ChannelHandle());
 }
 
 void GpuProcessHost::OnControlMessageReceived(const IPC::Message& message) {
@@ -146,21 +144,19 @@
     const IPC::ChannelHandle& channel_handle) {
   const ChannelRequest& request = sent_requests_.front();
 
-  ReplyToRenderer(request.renderer_id, request.routing_id, channel_handle);
+  ReplyToRenderer(request.renderer_id, channel_handle);
   sent_requests_.pop();
 }
 
 void GpuProcessHost::ReplyToRenderer(
     int renderer_id,
-    int routing_id,
     const IPC::ChannelHandle& channel) {
   // Check whether the renderer process is still around.
   RenderProcessHost* process_host = RenderProcessHost::FromID(renderer_id);
   if (!process_host)
     return;
 
-  CHECK(process_host->Send(new ViewMsg_GpuChannelEstablished(routing_id,
-                                                             channel)));
+  CHECK(process_host->Send(new ViewMsg_GpuChannelEstablished(channel)));
 }
 
 void GpuProcessHost::PropagateBrowserCommandLineToGpu(
diff --git a/chrome/browser/gpu_process_host.h b/chrome/browser/gpu_process_host.h
index 36d068f..4d74e00 100644
--- a/chrome/browser/gpu_process_host.h
+++ b/chrome/browser/gpu_process_host.h
@@ -13,6 +13,7 @@
 #include "chrome/browser/child_process_launcher.h"
 #include "chrome/common/gpu_native_window_handle.h"
 #include "chrome/common/message_router.h"
+#include "gfx/native_widget_types.h"
 #include "ipc/ipc_channel_handle.h"
 #include "ipc/ipc_channel_proxy.h"
 
@@ -50,26 +51,20 @@
   // Tells the GPU process to create a new channel for communication with a
   // renderer. Will asynchronously send message to object with given routing id
   // on completion.
-  void EstablishGpuChannel(int renderer_id, int routing_id);
+  void EstablishGpuChannel(int renderer_id);
 
  private:
   friend struct DefaultSingletonTraits<GpuProcessHost>;
 
   // Used to queue pending channel requests.
   struct ChannelRequest {
-    ChannelRequest(int renderer_id,
-                   int routing_id) :
-        renderer_id(renderer_id),
-        routing_id(routing_id) {}
+    explicit ChannelRequest(int renderer_id) : renderer_id(renderer_id) {}
     // Used to identify the renderer. The ID is used instead of a pointer to
     // the RenderProcessHost in case it is destroyed while the request is
     // pending.
     // TODO(apatrick): investigate whether these IDs are used for future
     // render processes.
     int renderer_id;
-
-    // Routing ID of object to receive reply message.
-    int routing_id;
   };
 
   GpuProcessHost();
@@ -81,7 +76,6 @@
   void OnChannelEstablished(const IPC::ChannelHandle& channel_handle);
 
   void ReplyToRenderer(int renderer_id,
-                       int routing_id,
                        const IPC::ChannelHandle& channel);
 
   // These are the channel requests that we have already sent to
diff --git a/chrome/browser/renderer_host/browser_render_process_host.cc b/chrome/browser/renderer_host/browser_render_process_host.cc
index 0757d17..dccc34d3 100644
--- a/chrome/browser/renderer_host/browser_render_process_host.cc
+++ b/chrome/browser/renderer_host/browser_render_process_host.cc
@@ -29,6 +29,7 @@
 #include "chrome/browser/extensions/extension_message_service.h"
 #include "chrome/browser/extensions/extensions_service.h"
 #include "chrome/browser/extensions/user_script_master.h"
+#include "chrome/browser/gpu_process_host.h"
 #include "chrome/browser/history/history.h"
 #include "chrome/browser/io_thread.h"
 #include "chrome/browser/net/url_request_context_getter.h"
@@ -45,6 +46,7 @@
 #include "chrome/browser/visitedlink_master.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/child_process_info.h"
+#include "chrome/common/gpu_messages.h"
 #include "chrome/common/logging_chrome.h"
 #include "chrome/common/notification_service.h"
 #include "chrome/common/pref_names.h"
@@ -770,6 +772,8 @@
                           OnExtensionRemoveListener)
       IPC_MESSAGE_HANDLER(ViewHostMsg_ExtensionCloseChannel,
                           OnExtensionCloseChannel)
+      IPC_MESSAGE_HANDLER(ViewHostMsg_EstablishGpuChannel,
+          OnMsgEstablishGpuChannel)
       IPC_MESSAGE_HANDLER(ViewHostMsg_SpellChecker_RequestDictionary,
                           OnSpellCheckerRequestDictionary)
       IPC_MESSAGE_UNHANDLED_ERROR()
@@ -974,6 +978,10 @@
   }
 }
 
+void BrowserRenderProcessHost::OnMsgEstablishGpuChannel() {
+  GpuProcessHost::Get()->EstablishGpuChannel(id());
+}
+
 void BrowserRenderProcessHost::OnSpellCheckerRequestDictionary() {
   if (profile()->GetSpellCheckHost()) {
     // The spellchecker initialization already started and finished; just send
diff --git a/chrome/browser/renderer_host/browser_render_process_host.h b/chrome/browser/renderer_host/browser_render_process_host.h
index 277a53e4..372045a 100644
--- a/chrome/browser/renderer_host/browser_render_process_host.h
+++ b/chrome/browser/renderer_host/browser_render_process_host.h
@@ -110,6 +110,7 @@
   void OnExtensionAddListener(const std::string& event_name);
   void OnExtensionRemoveListener(const std::string& event_name);
   void OnExtensionCloseChannel(int port_id);
+  void OnMsgEstablishGpuChannel();
 
   // Initialize support for visited links. Send the renderer process its initial
   // set of visited links.
diff --git a/chrome/browser/renderer_host/render_widget_host.cc b/chrome/browser/renderer_host/render_widget_host.cc
index 152aae14..5dc9badc 100644
--- a/chrome/browser/renderer_host/render_widget_host.cc
+++ b/chrome/browser/renderer_host/render_widget_host.cc
@@ -8,7 +8,6 @@
 #include "base/histogram.h"
 #include "base/keyboard_codes.h"
 #include "base/message_loop.h"
-#include "chrome/browser/gpu_process_host.h"
 #include "chrome/browser/renderer_host/backing_store.h"
 #include "chrome/browser/renderer_host/backing_store_manager.h"
 #include "chrome/browser/renderer_host/render_process_host.h"
@@ -16,7 +15,6 @@
 #include "chrome/browser/renderer_host/render_widget_host_painting_observer.h"
 #include "chrome/browser/renderer_host/render_widget_host_view.h"
 #include "chrome/browser/renderer_host/video_layer.h"
-#include "chrome/common/gpu_messages.h"
 #include "chrome/common/notification_service.h"
 #include "chrome/common/render_messages.h"
 #include "webkit/glue/webcursor.h"
@@ -139,8 +137,6 @@
     IPC_MESSAGE_HANDLER(ViewHostMsg_FocusedNodeChanged, OnMsgFocusedNodeChanged)
     IPC_MESSAGE_HANDLER(ViewHostMsg_SetCursor, OnMsgSetCursor)
     IPC_MESSAGE_HANDLER(ViewHostMsg_ImeUpdateStatus, OnMsgImeUpdateStatus)
-    IPC_MESSAGE_HANDLER(ViewHostMsg_EstablishGpuChannel,
-        OnMsgEstablishGpuChannel)
 #if defined(OS_LINUX)
     IPC_MESSAGE_HANDLER(ViewHostMsg_CreatePluginContainer,
                         OnMsgCreatePluginContainer)
@@ -869,10 +865,6 @@
   }
 }
 
-void RenderWidgetHost::OnMsgEstablishGpuChannel() {
-  GpuProcessHost::Get()->EstablishGpuChannel(process_->id(), routing_id_);
-}
-
 #if defined(OS_LINUX)
 
 void RenderWidgetHost::OnMsgCreatePluginContainer(gfx::PluginWindowHandle id) {
diff --git a/chrome/browser/renderer_host/render_widget_host.h b/chrome/browser/renderer_host/render_widget_host.h
index 5c95535..f8b8218 100644
--- a/chrome/browser/renderer_host/render_widget_host.h
+++ b/chrome/browser/renderer_host/render_widget_host.h
@@ -445,10 +445,6 @@
   // having to bring in render_messages.h in a header file.
   void OnMsgImeUpdateStatus(int control, const gfx::Rect& caret_rect);
 
-  // Renderer process is requesting that the browser process establish a GPU
-  // channel.
-  void OnMsgEstablishGpuChannel();
-
 #if defined(OS_LINUX)
   void OnMsgCreatePluginContainer(gfx::PluginWindowHandle id);
   void OnMsgDestroyPluginContainer(gfx::PluginWindowHandle id);
diff --git a/chrome/browser/renderer_host/render_widget_host_view_win.cc b/chrome/browser/renderer_host/render_widget_host_view_win.cc
index 55e2762..2d08f426 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_win.cc
+++ b/chrome/browser/renderer_host/render_widget_host_view_win.cc
@@ -293,6 +293,15 @@
 
 void RenderWidgetHostViewWin::CreateWnd(HWND parent) {
   Create(parent);  // ATL function to create the window.
+
+  // Add a property indicating that a particular renderer is associated with
+  // this window. Used by the GPU process to validate window handles it
+  // receives from renderer processes.
+  int renderer_id = render_widget_host_->process()->id();
+  SetProp(m_hWnd,
+          chrome::kChromiumRendererIdProperty,
+          reinterpret_cast<HANDLE>(renderer_id));
+
   // Uncommenting this will enable experimental out-of-process painting.
   // Contact brettw for more,
   // gpu_view_host_.reset(new GpuViewHost(render_widget_host_, m_hWnd));
diff --git a/chrome/common/chrome_constants.cc b/chrome/common/chrome_constants.cc
index 38cf59e5b..a6ad651 100644
--- a/chrome/common/chrome_constants.cc
+++ b/chrome/common/chrome_constants.cc
@@ -134,6 +134,8 @@
 
 const int kMaxSessionHistoryEntries = 50;
 
+const wchar_t kChromiumRendererIdProperty[] = L"ChromiumRendererId";
+
 }  // namespace chrome
 
 #undef FPL
diff --git a/chrome/common/chrome_constants.h b/chrome/common/chrome_constants.h
index 554a48b..456cea0 100644
--- a/chrome/common/chrome_constants.h
+++ b/chrome/common/chrome_constants.h
@@ -83,6 +83,8 @@
 // The maximum number of session history entries per tab.
 extern const int kMaxSessionHistoryEntries;
 
+extern const wchar_t kChromiumRendererIdProperty[];
+
 }  // namespace chrome
 
 #endif  // CHROME_COMMON_CHROME_CONSTANTS_H_
diff --git a/chrome/common/gpu_messages_internal.h b/chrome/common/gpu_messages_internal.h
index 6780a5ea..9107aed 100644
--- a/chrome/common/gpu_messages_internal.h
+++ b/chrome/common/gpu_messages_internal.h
@@ -9,6 +9,7 @@
 // This file needs to be included again, even though we're actually included
 // from it via utility_messages.h.
 #include "base/shared_memory.h"
+#include "gfx/size.h"
 #include "ipc/ipc_channel_handle.h"
 #include "ipc/ipc_message_macros.h"
 
@@ -96,9 +97,21 @@
 // These are messages from a renderer process to the GPU process.
 IPC_BEGIN_MESSAGES(GpuChannel)
 
-  // Tells the GPU process to create a new command buffer with the given
-  // id.  A corresponding GpuCommandBufferStub is created.
-  IPC_SYNC_MESSAGE_CONTROL0_1(GpuChannelMsg_CreateCommandBuffer,
+  // Tells the GPU process to create a new command buffer that renders directly
+  // to a native view. A corresponding GpuCommandBufferStub is created.
+  IPC_SYNC_MESSAGE_CONTROL1_1(GpuChannelMsg_CreateViewCommandBuffer,
+                              gfx::NativeViewId, /* view */
+                              int32 /* route_id */)
+
+  // Tells the GPU process to create a new command buffer that renders to an
+  // offscreen frame buffer. If parent_route_id is not zero, the texture backing
+  // the frame buffer is mapped into the corresponding parent command buffer's
+  // namespace, with the name of parent_texture_id. This ID is in the parent's
+  // namespace.
+  IPC_SYNC_MESSAGE_CONTROL3_1(GpuChannelMsg_CreateOffscreenCommandBuffer,
+                              int32, /* parent_route_id */
+                              gfx::Size, /* size */
+                              uint32, /* parent_texture_id */
                               int32 /* route_id */)
 
   // The CommandBufferProxy sends this to the GpuCommandBufferStub in its
@@ -168,6 +181,10 @@
   // repainted.
   IPC_MESSAGE_ROUTED0(GpuCommandBufferMsg_NotifyRepaint)
 
+  // Tells the GPU process to resize an offscreen frame buffer.
+  IPC_MESSAGE_ROUTED1(GpuCommandBufferMsg_ResizeOffscreenFrameBuffer,
+                      gfx::Size /* size */)
+
 #if defined(OS_MACOSX)
   // On Mac OS X the GPU plugin must be offscreen, because there is no
   // true cross-process window hierarchy. For this reason we must send
diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h
index a0f5d14..0372a49 100644
--- a/chrome/common/render_messages_internal.h
+++ b/chrome/common/render_messages_internal.h
@@ -604,8 +604,8 @@
 
   // The browser sends this to a renderer process in response to a
   // ViewHostMsg_EstablishGpuChannel message.
-  IPC_MESSAGE_ROUTED1(ViewMsg_GpuChannelEstablished,
-                      IPC::ChannelHandle /* handle to channel */)
+  IPC_MESSAGE_CONTROL1(ViewMsg_GpuChannelEstablished,
+                       IPC::ChannelHandle /* handle to channel */)
 
   // Notifies the renderer of the appcache that has been selected for a
   // a particular host. This is sent in reply to AppCacheMsg_SelectCache.
@@ -1340,7 +1340,7 @@
   // create connect to the GPU.  The browser will create the GPU process if
   // necessary, and will return a handle to the channel via
   // a GpuChannelEstablished message.
-  IPC_MESSAGE_ROUTED0(ViewHostMsg_EstablishGpuChannel)
+  IPC_MESSAGE_CONTROL0(ViewHostMsg_EstablishGpuChannel)
 
   // A renderer sends this to the browser process when it wants to start
   // a new instance of the Native Client process. The browser will launch
diff --git a/chrome/gpu/gpu_channel.cc b/chrome/gpu/gpu_channel.cc
index 4269022..b2b5bf9 100644
--- a/chrome/gpu/gpu_channel.cc
+++ b/chrome/gpu/gpu_channel.cc
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
 #include "chrome/gpu/gpu_channel.h"
 
 #include "base/command_line.h"
@@ -11,6 +15,7 @@
 #include "base/waitable_event.h"
 #include "build/build_config.h"
 #include "chrome/common/child_process.h"
+#include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/gpu_messages.h"
 #include "chrome/gpu/gpu_thread.h"
@@ -27,44 +32,12 @@
   }
 };
 
-typedef base::hash_map<std::string, scoped_refptr<GpuChannel> >
-    GpuChannelMap;
-
 // How long we wait before releasing the GPU process.
 const int kGpuReleaseTimeMS = 10000;
-
-GpuChannelMap g_gpu_channels;
 }  // namespace anonymous
 
-GpuChannel* GpuChannel::EstablishGpuChannel(int renderer_id) {
-  // Map renderer ID to a (single) channel to that process.
-  std::string channel_name = StringPrintf(
-      "%d.r%d", base::GetCurrentProcId(), renderer_id);
-
-  scoped_refptr<GpuChannel> channel;
-
-  GpuChannelMap::const_iterator iter = g_gpu_channels.find(channel_name);
-  if (iter == g_gpu_channels.end()) {
-    channel = new GpuChannel;
-  } else {
-    channel = iter->second;
-  }
-
-  DCHECK(channel != NULL);
-
-  if (!channel->channel_.get()) {
-    if (channel->Init(channel_name)) {
-      g_gpu_channels[channel_name] = channel;
-    } else {
-      channel = NULL;
-    }
-  }
-
-  return channel.get();
-}
-
-GpuChannel::GpuChannel()
-    : renderer_id_(-1)
+GpuChannel::GpuChannel(int renderer_id)
+    : renderer_id_(renderer_id)
 #if defined(OS_POSIX)
     , renderer_fd_(-1)
 #endif
@@ -141,8 +114,10 @@
 
 void GpuChannel::OnControlMessageReceived(const IPC::Message& msg) {
   IPC_BEGIN_MESSAGE_MAP(GpuChannel, msg)
-    IPC_MESSAGE_HANDLER(GpuChannelMsg_CreateCommandBuffer,
-        OnCreateCommandBuffer)
+    IPC_MESSAGE_HANDLER(GpuChannelMsg_CreateViewCommandBuffer,
+        OnCreateViewCommandBuffer)
+    IPC_MESSAGE_HANDLER(GpuChannelMsg_CreateOffscreenCommandBuffer,
+        OnCreateOffscreenCommandBuffer)
     IPC_MESSAGE_HANDLER(GpuChannelMsg_DestroyCommandBuffer,
         OnDestroyCommandBuffer)
     IPC_MESSAGE_UNHANDLED_ERROR()
@@ -154,11 +129,57 @@
   return ++last_id;
 }
 
-void GpuChannel::OnCreateCommandBuffer(int* route_id) {
+void GpuChannel::OnCreateViewCommandBuffer(gfx::NativeViewId view_id,
+                                           int32* route_id) {
+  *route_id = 0;
+
 #if defined(ENABLE_GPU)
+
+#if defined(OS_WIN)
+  gfx::NativeView view = gfx::NativeViewFromId(view_id);
+
+  // Check that the calling renderer is allowed to render to this window.
+  // TODO(apatrick): consider killing the renderer process rather than failing.
+  int view_renderer_id = reinterpret_cast<int>(
+      GetProp(view, chrome::kChromiumRendererIdProperty));
+  if (view_renderer_id != renderer_id_)
+    return;
+#else
+  // TODO(apatrick): This needs to be something valid for mac and linux.
+  // Offscreen rendering will work on these platforms but not rendering to the
+  // window.
+  DCHECK_EQ(view_id, 0);
+  gfx::NativeView view = 0;
+#endif
+
   *route_id = GenerateRouteID();
   scoped_refptr<GpuCommandBufferStub> stub = new GpuCommandBufferStub(
-      this, *route_id);
+      this, view, NULL, gfx::Size(), 0, *route_id);
+  router_.AddRoute(*route_id, stub);
+  stubs_[*route_id] = stub;
+#endif  // ENABLE_GPU
+}
+
+void GpuChannel::OnCreateOffscreenCommandBuffer(int32 parent_route_id,
+                                                const gfx::Size& size,
+                                                uint32 parent_texture_id,
+                                                int32* route_id) {
+#if defined(ENABLE_GPU)
+  *route_id = GenerateRouteID();
+  scoped_refptr<GpuCommandBufferStub> parent_stub;
+  if (parent_route_id != 0) {
+    StubMap::iterator it = stubs_.find(parent_route_id);
+    DCHECK(it != stubs_.end());
+    parent_stub = it->second;
+  }
+
+  scoped_refptr<GpuCommandBufferStub> stub = new GpuCommandBufferStub(
+      this,
+      NULL,
+      parent_stub.get(),
+      size,
+      parent_texture_id,
+      *route_id);
   router_.AddRoute(*route_id, stub);
   stubs_[*route_id] = stub;
 #else
@@ -166,7 +187,7 @@
 #endif
 }
 
-void GpuChannel::OnDestroyCommandBuffer(int route_id) {
+void GpuChannel::OnDestroyCommandBuffer(int32 route_id) {
 #if defined(ENABLE_GPU)
   StubMap::iterator it = stubs_.find(route_id);
   DCHECK(it != stubs_.end());
@@ -175,8 +196,13 @@
 #endif
 }
 
-bool GpuChannel::Init(const std::string& channel_name) {
-  channel_name_ = channel_name;
+bool GpuChannel::Init() {
+  // Check whether we're already initialized.
+  if (channel_.get())
+    return true;
+
+  // Map renderer ID to a (single) channel to that process.
+  std::string channel_name = GetChannelName();
 #if defined(OS_POSIX)
   // This gets called when the GpuChannel is initially created. At this
   // point, create the socketpair and assign the GPU side FD to the channel
@@ -192,3 +218,7 @@
       ChildProcess::current()->GetShutDownEvent()));
   return true;
 }
+
+std::string GpuChannel::GetChannelName() {
+  return StringPrintf("%d.r%d", base::GetCurrentProcId(), renderer_id_);
+}
diff --git a/chrome/gpu/gpu_channel.h b/chrome/gpu/gpu_channel.h
index 17ea363..60dc9067 100644
--- a/chrome/gpu/gpu_channel.h
+++ b/chrome/gpu/gpu_channel.h
@@ -13,6 +13,8 @@
 #include "build/build_config.h"
 #include "chrome/common/message_router.h"
 #include "chrome/gpu/gpu_command_buffer_stub.h"
+#include "gfx/native_widget_types.h"
+#include "gfx/size.h"
 #include "ipc/ipc_channel.h"
 #include "ipc/ipc_message.h"
 #include "ipc/ipc_sync_channel.h"
@@ -23,17 +25,12 @@
                    public IPC::Message::Sender,
                    public base::RefCountedThreadSafe<GpuChannel> {
  public:
-  // Get a new GpuChannel object for the current process to talk to the
-  // given renderer process. The renderer ID is an opaque unique ID generated
-  // by the browser.
-  //
-  // POSIX only: If |channel_fd| > 0, use that file descriptor for the
-  // channel socket.
-  static GpuChannel* EstablishGpuChannel(int renderer_id);
-
+  explicit GpuChannel(int renderer_id);
   virtual ~GpuChannel();
 
-  std::string channel_name() const { return channel_name_; }
+  bool Init();
+
+  std::string GetChannelName();
 
   base::ProcessHandle renderer_handle() const {
     return renderer_process_.handle();
@@ -60,21 +57,20 @@
   virtual bool Send(IPC::Message* msg);
 
  private:
-  // Called on the plugin thread
-  GpuChannel();
-
-  bool Init(const std::string& channel_name);
-
   void OnControlMessageReceived(const IPC::Message& msg);
 
   int GenerateRouteID();
 
   // Message handlers.
-  void OnCreateCommandBuffer(int* instance_id);
-  void OnDestroyCommandBuffer(int instance_id);
+  void OnCreateViewCommandBuffer(gfx::NativeViewId view,
+                                 int32* route_id);
+  void OnCreateOffscreenCommandBuffer(int32 parent_route_id,
+                                      const gfx::Size& size,
+                                      uint32 parent_texture_id,
+                                      int32* route_id);
+  void OnDestroyCommandBuffer(int32 route_id);
 
   scoped_ptr<IPC::SyncChannel> channel_;
-  std::string channel_name_;
 
   // Handle to the renderer process who is on the other side of the channel.
   base::ScopedOpenProcess renderer_process_;
@@ -92,7 +88,7 @@
   MessageRouter router_;
 
 #if defined(ENABLE_GPU)
-  typedef base::hash_map<int, scoped_refptr<GpuCommandBufferStub> > StubMap;
+  typedef base::hash_map<int32, scoped_refptr<GpuCommandBufferStub> > StubMap;
   StubMap stubs_;
 #endif
 
diff --git a/chrome/gpu/gpu_command_buffer_stub.cc b/chrome/gpu/gpu_command_buffer_stub.cc
index 6c3f5cc..5d6842d 100644
--- a/chrome/gpu/gpu_command_buffer_stub.cc
+++ b/chrome/gpu/gpu_command_buffer_stub.cc
@@ -6,6 +6,7 @@
 
 #include "base/process_util.h"
 #include "base/shared_memory.h"
+#include "build/build_config.h"
 #include "chrome/common/gpu_messages.h"
 #include "chrome/gpu/gpu_channel.h"
 #include "chrome/gpu/gpu_command_buffer_stub.h"
@@ -13,12 +14,23 @@
 using gpu::Buffer;
 
 GpuCommandBufferStub::GpuCommandBufferStub(GpuChannel* channel,
+                                           gfx::NativeView view,
+                                           GpuCommandBufferStub* parent,
+                                           const gfx::Size& size,
+                                           uint32 parent_texture_id,
                                            int32 route_id)
     : channel_(channel),
+      view_(view),
+      parent_(parent),
+      initial_size_(size),
+      parent_texture_id_(parent_texture_id),
       route_id_(route_id) {
 }
 
 GpuCommandBufferStub::~GpuCommandBufferStub() {
+  if (processor_.get()) {
+    processor_->Destroy();
+  }
 }
 
 void GpuCommandBufferStub::OnMessageReceived(const IPC::Message& message) {
@@ -34,6 +46,8 @@
                         OnDestroyTransferBuffer);
     IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_GetTransferBuffer,
                         OnGetTransferBuffer);
+    IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_ResizeOffscreenFrameBuffer,
+                        OnResizeOffscreenFrameBuffer);
     IPC_MESSAGE_UNHANDLED_ERROR()
   IPC_END_MESSAGE_MAP()
 }
@@ -55,8 +69,18 @@
   if (command_buffer_->Initialize(size)) {
     Buffer buffer = command_buffer_->GetRingBuffer();
     if (buffer.shared_memory) {
-      processor_ = new gpu::GPUProcessor(command_buffer_.get());
-      if (processor_->Initialize(gfx::kNullPluginWindow)) {
+      gpu::GPUProcessor* parent_processor =
+          parent_ ? parent_->processor_.get() : NULL;
+      processor_.reset(new gpu::GPUProcessor(command_buffer_.get()));
+      // TODO(apatrick): The reinterpret_cast below is only valid on windows.
+#if !defined(OS_WIN)
+      DCHECK_EQ(view_, static_cast<gfx::NativeView>(0));
+#endif
+      if (processor_->Initialize(
+          reinterpret_cast<gfx::PluginWindowHandle>(view_),
+          parent_processor,
+          initial_size_,
+          parent_texture_id_)) {
         command_buffer_->SetPutOffsetChangeCallback(
             NewCallback(processor_.get(),
                         &gpu::GPUProcessor::ProcessCommands));
@@ -66,7 +90,7 @@
         buffer.shared_memory->ShareToProcess(channel_->renderer_handle(),
                                              ring_buffer);
       } else {
-        processor_ = NULL;
+        processor_.reset();
         command_buffer_.reset();
       }
     }
@@ -117,4 +141,8 @@
   }
 }
 
+void GpuCommandBufferStub::OnResizeOffscreenFrameBuffer(const gfx::Size& size) {
+  processor_->ResizeOffscreenFrameBuffer(size);
+}
+
 #endif  // ENABLE_GPU
diff --git a/chrome/gpu/gpu_command_buffer_stub.h b/chrome/gpu/gpu_command_buffer_stub.h
index eb927a34..7aaf68a 100644
--- a/chrome/gpu/gpu_command_buffer_stub.h
+++ b/chrome/gpu/gpu_command_buffer_stub.h
@@ -10,6 +10,7 @@
 #include "base/process.h"
 #include "base/ref_counted.h"
 #include "gfx/native_widget_types.h"
+#include "gfx/size.h"
 #include "gpu/command_buffer/service/command_buffer_service.h"
 #include "gpu/command_buffer/service/gpu_processor.h"
 #include "ipc/ipc_channel.h"
@@ -23,6 +24,10 @@
       public base::RefCountedThreadSafe<GpuCommandBufferStub> {
  public:
   GpuCommandBufferStub(GpuChannel* channel,
+                       gfx::NativeView view,
+                       GpuCommandBufferStub* parent,
+                       const gfx::Size& size,
+                       uint32 parent_texture_id,
                        int32 route_id);
 
   virtual ~GpuCommandBufferStub();
@@ -33,7 +38,7 @@
   // IPC::Message::Sender implementation:
   virtual bool Send(IPC::Message* msg);
 
-  int route_id() const { return route_id_; }
+  int32 route_id() const { return route_id_; }
 
  private:
   // Message handlers:
@@ -47,12 +52,17 @@
   void OnGetTransferBuffer(int32 id,
                            base::SharedMemoryHandle* transfer_buffer,
                            uint32* size);
+  void OnResizeOffscreenFrameBuffer(const gfx::Size& size);
 
   scoped_refptr<GpuChannel> channel_;
-  int route_id_;
+  gfx::NativeView view_;
+  scoped_refptr<GpuCommandBufferStub> parent_;
+  gfx::Size initial_size_;
+  uint32 parent_texture_id_;
+  int32 route_id_;
 
   scoped_ptr<gpu::CommandBufferService> command_buffer_;
-  scoped_refptr<gpu::GPUProcessor> processor_;
+  scoped_ptr<gpu::GPUProcessor> processor_;
 
   DISALLOW_COPY_AND_ASSIGN(GpuCommandBufferStub);
 };
diff --git a/chrome/gpu/gpu_thread.cc b/chrome/gpu/gpu_thread.cc
index 1839412e..9de441c 100644
--- a/chrome/gpu/gpu_thread.cc
+++ b/chrome/gpu/gpu_thread.cc
@@ -7,7 +7,6 @@
 #include "build/build_config.h"
 #include "chrome/common/child_process.h"
 #include "chrome/common/gpu_messages.h"
-#include "chrome/gpu/gpu_channel.h"
 #include "chrome/gpu/gpu_config.h"
 
 #if defined(OS_WIN)
@@ -47,11 +46,28 @@
 }
 
 void GpuThread::OnEstablishChannel(int renderer_id) {
-  scoped_refptr<GpuChannel> channel =
-      GpuChannel::EstablishGpuChannel(renderer_id);
+  scoped_refptr<GpuChannel> channel;
+
+  GpuChannelMap::const_iterator iter = gpu_channels_.find(renderer_id);
+  if (iter == gpu_channels_.end()) {
+    channel = new GpuChannel(renderer_id);
+  } else {
+    channel = iter->second;
+  }
+
+  DCHECK(channel != NULL);
+
+  if (channel->Init()) {
+    // TODO(apatrick): figure out when to remove channels from the map. They
+    // will never be destroyed otherwise.
+    gpu_channels_[renderer_id] = channel;
+  } else {
+    channel = NULL;
+  }
+
   IPC::ChannelHandle channel_handle;
   if (channel.get()) {
-    channel_handle.name = channel->channel_name();
+    channel_handle.name = channel->GetChannelName();
 #if defined(OS_POSIX)
     // On POSIX, pass the renderer-side FD. Also mark it as auto-close so that
     // it gets closed after it has been sent.
diff --git a/chrome/gpu/gpu_thread.h b/chrome/gpu/gpu_thread.h
index c32b513e..23b8ba1d 100644
--- a/chrome/gpu/gpu_thread.h
+++ b/chrome/gpu/gpu_thread.h
@@ -10,6 +10,7 @@
 #include "build/build_config.h"
 #include "chrome/common/child_thread.h"
 #include "chrome/common/gpu_native_window_handle.h"
+#include "chrome/gpu/gpu_channel.h"
 #include "chrome/gpu/gpu_config.h"
 #include "chrome/gpu/x_util.h"
 #include "gfx/native_widget_types.h"
@@ -38,6 +39,9 @@
   void OnNewRenderWidgetHostView(GpuNativeWindowHandle parent_window,
                                  int32 routing_id);
 
+  typedef base::hash_map<int, scoped_refptr<GpuChannel> > GpuChannelMap;
+  GpuChannelMap gpu_channels_;
+
 #if defined(GPU_USE_GLX)
   Display* display_;
   scoped_ptr<GpuBackingStoreGLXContext> glx_context_;
diff --git a/chrome/plugin/command_buffer_stub.cc b/chrome/plugin/command_buffer_stub.cc
index 7dc049c..de18db6 100644
--- a/chrome/plugin/command_buffer_stub.cc
+++ b/chrome/plugin/command_buffer_stub.cc
@@ -92,8 +92,8 @@
   }
 
   // Initialize the GPUProcessor.
-  processor_ = new gpu::GPUProcessor(command_buffer_.get());
-  if (!processor_->Initialize(window_)) {
+  processor_.reset(new gpu::GPUProcessor(command_buffer_.get()));
+  if (!processor_->Initialize(window_, NULL, gfx::Size(), 0)) {
     Destroy();
     return;
   }
@@ -175,7 +175,7 @@
 }
 
 void CommandBufferStub::Destroy() {
-  processor_ = NULL;
+  processor_.reset();
   command_buffer_.reset();
 
   DestroyPlatformSpecific();
diff --git a/chrome/plugin/command_buffer_stub.h b/chrome/plugin/command_buffer_stub.h
index 026110f..83564ca7 100644
--- a/chrome/plugin/command_buffer_stub.h
+++ b/chrome/plugin/command_buffer_stub.h
@@ -73,7 +73,7 @@
   gfx::PluginWindowHandle window_;
   int route_id_;
   scoped_ptr<gpu::CommandBufferService> command_buffer_;
-  scoped_refptr<gpu::GPUProcessor> processor_;
+  scoped_ptr<gpu::GPUProcessor> processor_;
 };
 
 #endif  // ENABLE_GPU
diff --git a/chrome/renderer/command_buffer_proxy.cc b/chrome/renderer/command_buffer_proxy.cc
index bd0f3a0..4a624a3c 100644
--- a/chrome/renderer/command_buffer_proxy.cc
+++ b/chrome/renderer/command_buffer_proxy.cc
@@ -183,6 +183,10 @@
   NOTREACHED();
 }
 
+void CommandBufferProxy::ResizeOffscreenFrameBuffer(const gfx::Size& size) {
+  Send(new GpuCommandBufferMsg_ResizeOffscreenFrameBuffer(route_id_, size));
+}
+
 #if defined(OS_MACOSX)
 void CommandBufferProxy::SetWindowSize(int32 width, int32 height) {
   Send(new GpuCommandBufferMsg_SetWindowSize(route_id_, width, height));
diff --git a/chrome/renderer/command_buffer_proxy.h b/chrome/renderer/command_buffer_proxy.h
index 5d72f2c..e6fce0a 100644
--- a/chrome/renderer/command_buffer_proxy.h
+++ b/chrome/renderer/command_buffer_proxy.h
@@ -15,6 +15,7 @@
 #include "base/scoped_ptr.h"
 #include "base/shared_memory.h"
 #include "base/task.h"
+#include "gfx/size.h"
 #include "gpu/command_buffer/common/command_buffer.h"
 #include "ipc/ipc_channel.h"
 #include "ipc/ipc_message.h"
@@ -51,6 +52,9 @@
   virtual void SetToken(int32 token);
   virtual void SetParseError(gpu::error::Error error);
 
+  // Asynchronously resizes an offscreen frame buffer.
+  void ResizeOffscreenFrameBuffer(const gfx::Size& size);
+
   // Set a task that will be invoked the next time the window becomes invalid
   // and needs to be repainted. Takes ownership of task.
   void SetNotifyRepaintTask(Task* task) {
diff --git a/chrome/renderer/ggl/ggl.cc b/chrome/renderer/ggl/ggl.cc
index b5c1d9d..2c41126 100644
--- a/chrome/renderer/ggl/ggl.cc
+++ b/chrome/renderer/ggl/ggl.cc
@@ -50,12 +50,21 @@
 // Manages a GL context.
 class Context {
  public:
-  Context();
+  Context(GpuChannelHost* channel, Context* parent);
   ~Context();
 
   // Initialize a GGL context that can be used in association with a a GPU
   // channel acquired from a RenderWidget or RenderView.
-  bool Initialize(GpuChannelHost* channel);
+  bool Initialize(gfx::NativeViewId view, const gfx::Size& size);
+
+  // Asynchronously resizes an offscreen frame buffer.
+  void ResizeOffscreen(const gfx::Size& size);
+
+  // For an offscreen frame buffer context, return the frame buffer ID with
+  // respect to the parent.
+  uint32 parent_texture_id() const {
+    return parent_texture_id_;
+  }
 
   // Destroy all resources associated with the GGL context.
   void Destroy();
@@ -73,6 +82,8 @@
 
  private:
   scoped_refptr<GpuChannelHost> channel_;
+  Context* parent_;
+  uint32 parent_texture_id_;
   CommandBufferProxy* command_buffer_;
   gpu::gles2::GLES2CmdHelper* gles2_helper_;
   int32 transfer_buffer_id_;
@@ -81,31 +92,46 @@
   DISALLOW_COPY_AND_ASSIGN(Context);
 };
 
-Context::Context()
-    : channel_(NULL),
+Context::Context(GpuChannelHost* channel, Context* parent)
+    : channel_(channel),
+      parent_(parent),
+      parent_texture_id_(0),
       command_buffer_(NULL),
       gles2_helper_(NULL),
       transfer_buffer_id_(0),
       gles2_implementation_(NULL) {
+  DCHECK(channel);
 }
 
 Context::~Context() {
   Destroy();
 }
 
-bool Context::Initialize(GpuChannelHost* channel) {
-  DCHECK(channel);
+bool Context::Initialize(gfx::NativeViewId view, const gfx::Size& size) {
+  DCHECK(size.width() >= 0 && size.height() >= 0);
 
-  if (!channel->ready())
+  if (!channel_->ready())
     return false;
 
-  channel_ = channel;
-
   // Ensure the gles2 library is initialized first in a thread safe way.
   Singleton<GLES2Initializer>::get();
 
+  // Allocate a frame buffer ID with respect to the parent.
+  if (parent_) {
+    parent_->gles2_implementation_->MakeIds(1, &parent_texture_id_);
+  }
+
   // Create a proxy to a command buffer in the GPU process.
-  command_buffer_ = channel_->CreateCommandBuffer();
+  if (view) {
+    command_buffer_ = channel_->CreateViewCommandBuffer(view);
+  } else {
+    CommandBufferProxy* parent_command_buffer =
+        parent_ ? parent_->command_buffer_ : NULL;
+    command_buffer_ = channel_->CreateOffscreenCommandBuffer(
+        parent_command_buffer,
+        size,
+        parent_texture_id_);
+  }
   if (!command_buffer_) {
     Destroy();
     return false;
@@ -151,7 +177,15 @@
   return true;
 }
 
+void Context::ResizeOffscreen(const gfx::Size& size) {
+  DCHECK(size.width() > 0 && size.height() > 0);
+  command_buffer_->ResizeOffscreenFrameBuffer(size);
+}
+
 void Context::Destroy() {
+  if (parent_ && parent_texture_id_ != 0)
+    parent_->gles2_implementation_->FreeIds(1, &parent_texture_id_);
+
   delete gles2_implementation_;
   gles2_implementation_ = NULL;
 
@@ -213,10 +247,10 @@
 
 #endif  // ENABLE_GPU
 
-Context* CreateContext(GpuChannelHost* channel) {
+Context* CreateViewContext(GpuChannelHost* channel, gfx::NativeViewId view) {
 #if defined(ENABLE_GPU)
-  scoped_ptr<Context> context(new Context);
-  if (!context->Initialize(channel))
+  scoped_ptr<Context> context(new Context(channel, NULL));
+  if (!context->Initialize(view, gfx::Size()))
     return NULL;
 
   return context.release();
@@ -225,6 +259,34 @@
 #endif
 }
 
+Context* CreateOffscreenContext(GpuChannelHost* channel,
+                                Context* parent,
+                                const gfx::Size& size) {
+#if defined(ENABLE_GPU)
+  scoped_ptr<Context> context(new Context(channel, parent));
+  if (!context->Initialize(NULL, size))
+    return NULL;
+
+  return context.release();
+#else
+  return NULL;
+#endif
+}
+
+void ResizeOffscreenContext(Context* context, const gfx::Size& size) {
+#if defined(ENABLE_GPU)
+  context->ResizeOffscreen(size);
+#endif
+}
+
+uint32 GetParentTextureId(Context* context) {
+#if defined(ENABLE_GPU)
+  return context->parent_texture_id();
+#else
+  return 0;
+#endif
+}
+
 bool MakeCurrent(Context* context) {
 #if defined(ENABLE_GPU)
   return Context::MakeCurrent(context);
diff --git a/chrome/renderer/ggl/ggl.h b/chrome/renderer/ggl/ggl.h
index 26607ec..e5c661f 100644
--- a/chrome/renderer/ggl/ggl.h
+++ b/chrome/renderer/ggl/ggl.h
@@ -10,6 +10,9 @@
 #ifndef CHROME_RENDERER_GGL_GGL_H_
 #define CHROME_RENDERER_GGL_GGL_H_
 
+#include "gfx/native_widget_types.h"
+#include "gfx/size.h"
+
 class GpuChannelHost;
 
 namespace ggl {
@@ -32,8 +35,30 @@
 // have completed.
 bool Terminate();
 
-// Create A GGL context for an offscreen 1 x 1 pbuffer.
-Context* CreateContext(GpuChannelHost* channel);
+// Create a GGL context that renders directly to a view.
+Context* CreateViewContext(GpuChannelHost* channel, gfx::NativeViewId view);
+
+// Create a GGL context that renders to an offscreen frame buffer. If parent is
+// not NULL, that context can access a copy of the created
+// context's frame buffer that is updated every time SwapBuffers is called. It
+// is not as general as shared contexts in other implementations of OpenGL. If
+// parent is not NULL, it must be used on the same thread as the parent. A child
+// context may not outlive its parent.
+Context* CreateOffscreenContext(GpuChannelHost* channel,
+                                Context* parent,
+                                const gfx::Size& size);
+
+// Resize an offscreen frame buffer. The resize occurs on the next call to
+// SwapBuffers. This is to avoid waiting until all pending GL calls have been
+// executed by the GPU process. Everything rendered up to the call to
+// SwapBuffers will be lost. A lost context will be reported if the resize
+// fails.
+void ResizeOffscreenContext(Context* context, const gfx::Size& size);
+
+// For an offscreen frame buffer context, return the texture ID with
+// respect to the parent context. Returns zero if context does not have a
+// parent.
+uint32 GetParentTextureId(Context* context);
 
 // Set the current GGL context for the calling thread.
 bool MakeCurrent(Context* context);
@@ -41,7 +66,10 @@
 // Get the calling thread's current GGL context.
 Context* GetCurrentContext();
 
-// Display everything that has been rendered since the last call.
+// For a view context, display everything that has been rendered since the
+// last call. For an offscreen context, resolve everything that has been
+// rendered since the last call to a copy that can be accessed by the parent
+// context.
 bool SwapBuffers();
 
 // Destroy the given GGL context.
diff --git a/chrome/renderer/gpu_channel_host.cc b/chrome/renderer/gpu_channel_host.cc
index 8e48b85..8d2cdd9 100644
--- a/chrome/renderer/gpu_channel_host.cc
+++ b/chrome/renderer/gpu_channel_host.cc
@@ -64,14 +64,43 @@
   return channel_->Send(message);
 }
 
-CommandBufferProxy* GpuChannelHost::CreateCommandBuffer() {
+CommandBufferProxy* GpuChannelHost::CreateViewCommandBuffer(
+    gfx::NativeViewId view) {
 #if defined(ENABLE_GPU)
   // An error occurred. Need to get the host again to reinitialize it.
   if (!channel_.get())
     return NULL;
 
   int32 route_id;
-  if (!Send(new GpuChannelMsg_CreateCommandBuffer(&route_id)) &&
+  if (!Send(new GpuChannelMsg_CreateViewCommandBuffer(view, &route_id)) &&
+      route_id != MSG_ROUTING_NONE) {
+    return NULL;
+  }
+
+  CommandBufferProxy* command_buffer = new CommandBufferProxy(this, route_id);
+  router_.AddRoute(route_id, command_buffer);
+  proxies_[route_id] = command_buffer;
+  return command_buffer;
+#else
+  return NULL;
+#endif
+}
+
+CommandBufferProxy* GpuChannelHost::CreateOffscreenCommandBuffer(
+    CommandBufferProxy* parent,
+    const gfx::Size& size,
+    uint32 parent_texture_id) {
+#if defined(ENABLE_GPU)
+  // An error occurred. Need to get the host again to reinitialize it.
+  if (!channel_.get())
+    return NULL;
+
+  int32 parent_route_id = parent ? parent->route_id() : 0;
+  int32 route_id;
+  if (!Send(new GpuChannelMsg_CreateOffscreenCommandBuffer(parent_route_id,
+                                                           size,
+                                                           parent_texture_id,
+                                                           &route_id)) &&
       route_id != MSG_ROUTING_NONE) {
     return NULL;
   }
@@ -99,3 +128,4 @@
   delete command_buffer;
 #endif
 }
+
diff --git a/chrome/renderer/gpu_channel_host.h b/chrome/renderer/gpu_channel_host.h
index 00a6129c..4b982bfcb 100644
--- a/chrome/renderer/gpu_channel_host.h
+++ b/chrome/renderer/gpu_channel_host.h
@@ -9,6 +9,8 @@
 
 #include "base/hash_tables.h"
 #include "chrome/common/message_router.h"
+#include "gfx/native_widget_types.h"
+#include "gfx/size.h"
 #include "ipc/ipc_channel.h"
 #include "ipc/ipc_message.h"
 #include "ipc/ipc_sync_channel.h"
@@ -52,7 +54,13 @@
   virtual bool Send(IPC::Message* msg);
 
   // Create and connect to a command buffer in the GPU process.
-  CommandBufferProxy* CreateCommandBuffer();
+  CommandBufferProxy* CreateViewCommandBuffer(gfx::NativeViewId view);
+
+  // Create and connect to a command buffer in the GPU process.
+  CommandBufferProxy* CreateOffscreenCommandBuffer(CommandBufferProxy* parent,
+                                                   const gfx::Size& size,
+                                                   uint32 parent_texture_id);
+
 
   // Destroy a command buffer created by this channel.
   void DestroyCommandBuffer(CommandBufferProxy* command_buffer);
diff --git a/chrome/renderer/render_thread.cc b/chrome/renderer/render_thread.cc
index ab97f2a3..4fb0971f 100644
--- a/chrome/renderer/render_thread.cc
+++ b/chrome/renderer/render_thread.cc
@@ -90,6 +90,10 @@
 #include "chrome/app/breakpad_mac.h"
 #endif
 
+#if defined(OS_POSIX)
+#include "ipc/ipc_channel_posix.h"
+#endif
+
 using WebKit::WebCache;
 using WebKit::WebCrossOriginPreflightResultCache;
 using WebKit::WebFontCache;
@@ -565,6 +569,7 @@
                         OnSpellCheckWordAdded)
     IPC_MESSAGE_HANDLER(ViewMsg_SpellChecker_EnableAutoSpellCorrect,
                         OnSpellCheckEnableAutoSpellCorrect)
+    IPC_MESSAGE_HANDLER(ViewMsg_GpuChannelEstablished, OnGpuChannelEstablished)
   IPC_END_MESSAGE_MAP()
 }
 
@@ -680,6 +685,34 @@
   child_process_logging::SetActiveExtensions(active_extensions);
 }
 
+void RenderThread::EstablishGpuChannel() {
+  if (gpu_channel_.get()) {
+    // Do nothing if we are already establishing GPU channel.
+    if (gpu_channel_->state() == GpuChannelHost::UNCONNECTED)
+      return;
+
+    // Recreate the channel if it has been lost.
+    if (gpu_channel_->state() == GpuChannelHost::LOST)
+      gpu_channel_ = NULL;
+  }
+
+  if (!gpu_channel_.get())
+    gpu_channel_ = new GpuChannelHost;
+
+  // Ask the browser for the channel name.
+  Send(new ViewHostMsg_EstablishGpuChannel());
+}
+
+GpuChannelHost* RenderThread::GetGpuChannel() {
+  if (!gpu_channel_.get())
+    return NULL;
+
+  if (gpu_channel_->state() != GpuChannelHost::CONNECTED)
+    return NULL;
+
+  return gpu_channel_.get();
+}
+
 static void* CreateHistogram(
     const char *name, int min, int max, size_t buckets) {
   if (min <= 0)
@@ -940,3 +973,20 @@
 void RenderThread::OnSetIsIncognitoProcess(bool is_incognito_process) {
   is_incognito_process_ = is_incognito_process;
 }
+
+void RenderThread::OnGpuChannelEstablished(
+    const IPC::ChannelHandle& channel_handle) {
+#if defined(OS_POSIX)
+  // If we received a ChannelHandle, register it now.
+  if (channel_handle.socket.fd >= 0)
+    IPC::AddChannelSocket(channel_handle.name, channel_handle.socket.fd);
+#endif
+
+  if (channel_handle.name.size() != 0) {
+    // Connect to the GPU process if a channel name was received.
+    gpu_channel_->Connect(channel_handle.name);
+  } else {
+    // Otherwise cancel the connection.
+    gpu_channel_ = NULL;
+  }
+}
diff --git a/chrome/renderer/render_thread.h b/chrome/renderer/render_thread.h
index fea7e41..3caed00f 100644
--- a/chrome/renderer/render_thread.h
+++ b/chrome/renderer/render_thread.h
@@ -18,9 +18,11 @@
 #include "chrome/common/child_thread.h"
 #include "chrome/common/css_colors.h"
 #include "chrome/common/dom_storage_common.h"
+#include "chrome/renderer/gpu_channel_host.h"
 #include "chrome/renderer/renderer_histogram_snapshots.h"
 #include "chrome/renderer/visitedlink_slave.h"
 #include "gfx/native_widget_types.h"
+#include "ipc/ipc_channel_handle.h"
 #include "ipc/ipc_platform_file.h"
 
 class AppCacheDispatcher;
@@ -165,6 +167,15 @@
   // Update the list of active extensions that will be reported when we crash.
   void UpdateActiveExtensions();
 
+  // Asynchronously establish a channel to the GPU plugin if not previously
+  // established or if it has been lost (for example if the GPU plugin crashed).
+  // Use GetGpuChannel() to determine when the channel is ready for use.
+  void EstablishGpuChannel();
+
+  // Get the GPU channel. Returns NULL if the channel is not established or
+  // has been lost.
+  GpuChannelHost* GetGpuChannel();
+
  private:
   virtual void OnControlMessageReceived(const IPC::Message& msg);
 
@@ -220,6 +231,8 @@
   void OnSpellCheckWordAdded(const std::string& word);
   void OnSpellCheckEnableAutoSpellCorrect(bool enable);
 
+  void OnGpuChannelEstablished(const IPC::ChannelHandle& channel_handle);
+
   // Gather usage statistics from the in-memory cache and inform our host.
   // These functions should be call periodically so that the host can make
   // decisions about how to allocation resources using current information.
@@ -288,6 +301,9 @@
   // not idle, to ensure that IdleHandle gets called eventually.
   base::RepeatingTimer<RenderThread> forced_idle_timer_;
 
+  // The channel from the renderer process to the GPU process.
+  scoped_refptr<GpuChannelHost> gpu_channel_;
+
   DISALLOW_COPY_AND_ASSIGN(RenderThread);
 };
 
diff --git a/chrome/renderer/render_widget.cc b/chrome/renderer/render_widget.cc
index def6e477..50af08fe 100644
--- a/chrome/renderer/render_widget.cc
+++ b/chrome/renderer/render_widget.cc
@@ -150,7 +150,6 @@
   IPC_MESSAGE_HANDLER(ViewMsg_ImeSetComposition, OnImeSetComposition)
   IPC_MESSAGE_HANDLER(ViewMsg_Repaint, OnMsgRepaint)
   IPC_MESSAGE_HANDLER(ViewMsg_SetTextDirection, OnSetTextDirection)
-  IPC_MESSAGE_HANDLER(ViewMsg_GpuChannelEstablished, OnGpuChannelEstablished)
   IPC_MESSAGE_HANDLER(ViewMsg_Move_ACK, OnRequestMoveAck)
   IPC_MESSAGE_UNHANDLED_ERROR()
 IPC_END_MESSAGE_MAP()
@@ -732,23 +731,6 @@
   webwidget_->setTextDirection(direction);
 }
 
-void RenderWidget::OnGpuChannelEstablished(
-    const IPC::ChannelHandle& channel_handle) {
-#if defined(OS_POSIX)
-  // If we received a ChannelHandle, register it now.
-  if (channel_handle.socket.fd >= 0)
-    IPC::AddChannelSocket(channel_handle.name, channel_handle.socket.fd);
-#endif
-
-  if (channel_handle.name.size() != 0) {
-    // Connect to the GPU process if a channel name was received.
-    gpu_channel_->Connect(channel_handle.name);
-  } else {
-    // Otherwise cancel the connection.
-    gpu_channel_ = NULL;
-  }
-}
-
 void RenderWidget::SetHidden(bool hidden) {
   if (is_hidden_ == hidden)
     return;
@@ -892,31 +874,3 @@
   }
 }
 
-void RenderWidget::EstablishGpuChannel() {
-  if (gpu_channel_.get()) {
-    // Do nothing if we are already establishing GPU channel.
-    if (gpu_channel_->state() == GpuChannelHost::UNCONNECTED)
-      return;
-
-    // Recreate the channel if it has been lost.
-    if (gpu_channel_->state() == GpuChannelHost::LOST)
-      gpu_channel_ = NULL;
-  }
-
-  if (!gpu_channel_.get())
-    gpu_channel_ = new GpuChannelHost;
-
-  // Ask the browser for the channel name.
-  CHECK(Send(new ViewHostMsg_EstablishGpuChannel(routing_id_)));
-}
-
-GpuChannelHost* RenderWidget::GetGpuChannel() {
-  if (!gpu_channel_.get())
-    return NULL;
-
-  if (gpu_channel_->state() != GpuChannelHost::CONNECTED)
-    return NULL;
-
-  return gpu_channel_.get();
-}
-
diff --git a/chrome/renderer/render_widget.h b/chrome/renderer/render_widget.h
index 4759e301..47817903 100644
--- a/chrome/renderer/render_widget.h
+++ b/chrome/renderer/render_widget.h
@@ -10,7 +10,6 @@
 #include "base/basictypes.h"
 #include "base/ref_counted.h"
 #include "base/shared_memory.h"
-#include "chrome/renderer/gpu_channel_host.h"
 #include "chrome/renderer/paint_aggregator.h"
 #include "chrome/renderer/render_process.h"
 #include "gfx/native_widget_types.h"
@@ -18,7 +17,6 @@
 #include "gfx/rect.h"
 #include "gfx/size.h"
 #include "ipc/ipc_channel.h"
-#include "ipc/ipc_channel_handle.h"
 #include "skia/ext/platform_canvas.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/WebKit/WebKit/chromium/public/WebCompositionCommand.h"
@@ -107,15 +105,6 @@
   // Close the underlying WebWidget.
   virtual void Close();
 
-  // Asynchronously establish a channel to the GPU plugin if not previously
-  // established or if it has been lost (for example if the GPU plugin crashed).
-  // Use GetGpuChannel() to determine when the channel is ready for use.
-  void EstablishGpuChannel();
-
-  // Get the GPU channel. Returns NULL if the channel is not established or
-  // has been lost.
-  GpuChannelHost* GetGpuChannel();
-
  protected:
   // Friend RefCounted so that the dtor can be non-public. Using this class
   // without ref-counting is an error.
@@ -171,7 +160,6 @@
                            const string16& ime_string);
   void OnMsgRepaint(const gfx::Size& size_to_paint);
   void OnSetTextDirection(WebKit::WebTextDirection direction);
-  void OnGpuChannelEstablished(const IPC::ChannelHandle& channel_handle);
 
   // Override point to notify derived classes that a paint has happened.
   // DidInitiatePaint happens when we've generated a new bitmap and sent it to
@@ -332,9 +320,6 @@
   // Indicates if the next sequence of Char events should be suppressed or not.
   bool suppress_next_char_events_;
 
-  // The channel from the renderer process to the GPU process.
-  scoped_refptr<GpuChannelHost> gpu_channel_;
-
   DISALLOW_COPY_AND_ASSIGN(RenderWidget);
 };
 
diff --git a/gpu/command_buffer/build_gles2_cmd_buffer.py b/gpu/command_buffer/build_gles2_cmd_buffer.py
index 16f084bc..6d11c08 100755
--- a/gpu/command_buffer/build_gles2_cmd_buffer.py
+++ b/gpu/command_buffer/build_gles2_cmd_buffer.py
@@ -1178,6 +1178,11 @@
     'cmd_args':
         'GLuint shader, const char* data',
   },
+  'SwapBuffers': {
+    'type': 'Custom',
+    'impl_func': False,
+    'unit_test': False,
+  },
   'TexImage2D': {'type': 'Manual', 'immediate': True},
   'TexParameterf': {'decoder_func': 'DoTexParameterf'},
   'TexParameteri': {'decoder_func': 'DoTexParameteri'},
@@ -1222,11 +1227,6 @@
       'cmd_args': 'GLuint indx, GLint size, GLenum type, GLboolean normalized, '
                   'GLsizei stride, GLuint offset',
   },
-  'SwapBuffers': {
-    'impl_func': False,
-    'decoder_func': 'DoSwapBuffers',
-    'unit_test': False,
-  },
 }
 
 
diff --git a/gpu/command_buffer/client/cmd_buffer_helper_test.cc b/gpu/command_buffer/client/cmd_buffer_helper_test.cc
index ffb81377..d754f5b 100644
--- a/gpu/command_buffer/client/cmd_buffer_helper_test.cc
+++ b/gpu/command_buffer/client/cmd_buffer_helper_test.cc
@@ -51,12 +51,12 @@
                                 0,
                                 api_mock_.get());
 
-    scoped_refptr<GPUProcessor> gpu_processor(new GPUProcessor(
+    gpu_processor_.reset(new GPUProcessor(
         command_buffer_.get(), NULL, parser_, 1));
     command_buffer_->SetPutOffsetChangeCallback(NewCallback(
-        gpu_processor.get(), &GPUProcessor::ProcessCommands));
+        gpu_processor_.get(), &GPUProcessor::ProcessCommands));
 
-    api_mock_->set_engine(gpu_processor.get());
+    api_mock_->set_engine(gpu_processor_.get());
 
     helper_.reset(new CommandBufferHelper(command_buffer_.get()));
     helper_->Initialize();
@@ -132,6 +132,7 @@
   MessageLoop message_loop_;
   scoped_ptr<AsyncAPIMock> api_mock_;
   scoped_ptr<CommandBufferService> command_buffer_;
+  scoped_ptr<GPUProcessor> gpu_processor_;
   CommandParser* parser_;
   scoped_ptr<CommandBufferHelper> helper_;
   Sequence sequence_;
diff --git a/gpu/command_buffer/client/fenced_allocator_test.cc b/gpu/command_buffer/client/fenced_allocator_test.cc
index c47f355..8dc1b780 100644
--- a/gpu/command_buffer/client/fenced_allocator_test.cc
+++ b/gpu/command_buffer/client/fenced_allocator_test.cc
@@ -52,12 +52,12 @@
                                 0,
                                 api_mock_.get());
 
-    scoped_refptr<GPUProcessor> gpu_processor(new GPUProcessor(
+    gpu_processor_.reset(new GPUProcessor(
         command_buffer_.get(), NULL, parser_, INT_MAX));
     command_buffer_->SetPutOffsetChangeCallback(NewCallback(
-        gpu_processor.get(), &GPUProcessor::ProcessCommands));
+        gpu_processor_.get(), &GPUProcessor::ProcessCommands));
 
-    api_mock_->set_engine(gpu_processor.get());
+    api_mock_->set_engine(gpu_processor_.get());
 
     helper_.reset(new CommandBufferHelper(command_buffer_.get()));
     helper_->Initialize();
@@ -76,6 +76,7 @@
   MessageLoop message_loop_;
   scoped_ptr<AsyncAPIMock> api_mock_;
   scoped_ptr<CommandBufferService> command_buffer_;
+  scoped_ptr<GPUProcessor> gpu_processor_;
   CommandParser* parser_;
   scoped_ptr<CommandBufferHelper> helper_;
 };
diff --git a/gpu/command_buffer/client/gles2_demo.cc b/gpu/command_buffer/client/gles2_demo.cc
index 41c42b1..6035042 100644
--- a/gpu/command_buffer/client/gles2_demo.cc
+++ b/gpu/command_buffer/client/gles2_demo.cc
@@ -54,14 +54,16 @@
   if (!command_buffer->Initialize(size))
     return NULL;
 
-  scoped_refptr<GPUProcessor> gpu_processor(
-      new GPUProcessor(command_buffer.get()));
-  if (!gpu_processor->Initialize(reinterpret_cast<HWND>(hwnd))) {
+  GPUProcessor* gpu_processor = new GPUProcessor(command_buffer.get());
+  if (!gpu_processor->Initialize(reinterpret_cast<HWND>(hwnd),
+                                 NULL,
+                                 gfx::Size(),
+                                 0)) {
     return NULL;
   }
 
   command_buffer->SetPutOffsetChangeCallback(
-      NewCallback(gpu_processor.get(), &GPUProcessor::ProcessCommands));
+      NewCallback(gpu_processor, &GPUProcessor::ProcessCommands));
 
   GLES2CmdHelper* helper = new GLES2CmdHelper(command_buffer.get());
   if (!helper->Initialize()) {
diff --git a/gpu/command_buffer/client/gles2_implementation.h b/gpu/command_buffer/client/gles2_implementation.h
index c1ee9c6..fd986869 100644
--- a/gpu/command_buffer/client/gles2_implementation.h
+++ b/gpu/command_buffer/client/gles2_implementation.h
@@ -43,13 +43,13 @@
   // this file instead of having to edit some template or the code generator.
   #include "../client/gles2_implementation_autogen.h"
 
- private:
   // Makes a set of Ids for glGen___ functions.
   void MakeIds(GLsizei n, GLuint* ids);
 
   // Frees a set of Ids for glDelete___ functions.
   void FreeIds(GLsizei n, const GLuint* ids);
 
+ private:
   // Gets the shared memory id for the result buffer.
   uint32 result_shm_id() const {
     return transfer_buffer_id_;
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 512a819..23c3423 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -14,6 +14,7 @@
 #include "base/callback.h"
 #include "base/linked_ptr.h"
 #include "base/scoped_ptr.h"
+#include "base/weak_ptr.h"
 #define GLES2_GPU_SERVICE 1
 #include "gpu/command_buffer/common/gles2_cmd_format.h"
 #include "gpu/command_buffer/common/gles2_cmd_utils.h"
@@ -36,9 +37,15 @@
 #include "app/surface/accelerated_surface_mac.h"
 #endif
 
+#if !defined(GL_DEPTH24_STENCIL8)
+#define GL_DEPTH24_STENCIL8 0x88F0
+#endif
+
 namespace gpu {
 namespace gles2 {
 
+class GLES2DecoderImpl;
+
 // Check that certain assumptions the code makes are true. There are places in
 // the code where shared memory is passed direclty to GL. Example, glUniformiv,
 // glShaderSource. The command buffer code assumes GLint and GLsizei (and maybe
@@ -107,6 +114,144 @@
   #undef GLES2_CMD_OP
 };
 
+// This class prevents any GL errors that occur when it is in scope from
+// being reported to the client.
+class ScopedGLErrorSuppressor {
+ public:
+  explicit ScopedGLErrorSuppressor(GLES2DecoderImpl* decoder);
+  ~ScopedGLErrorSuppressor();
+ private:
+  GLES2DecoderImpl* decoder_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedGLErrorSuppressor);
+};
+
+// Temporarily changes a decoder's bound 2D texture and restore it when this
+// object goes out of scope. Also temporarily switches to using active texture
+// unit zero in case the client has changed that to something invalid.
+class ScopedTexture2DBinder {
+ public:
+  ScopedTexture2DBinder(GLES2DecoderImpl* decoder, GLuint id);
+  ~ScopedTexture2DBinder();
+
+ private:
+  GLES2DecoderImpl* decoder_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedTexture2DBinder);
+};
+
+// Temporarily changes a decoder's bound render buffer and restore it when this
+// object goes out of scope.
+class ScopedRenderBufferBinder {
+ public:
+  ScopedRenderBufferBinder(GLES2DecoderImpl* decoder, GLuint id);
+  ~ScopedRenderBufferBinder();
+
+ private:
+  GLES2DecoderImpl* decoder_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedRenderBufferBinder);
+};
+
+// Temporarily changes a decoder's bound frame buffer and restore it when this
+// object goes out of scope.
+class ScopedFrameBufferBinder {
+ public:
+  ScopedFrameBufferBinder(GLES2DecoderImpl* decoder, GLuint id);
+  ~ScopedFrameBufferBinder();
+
+ private:
+  GLES2DecoderImpl* decoder_;
+  DISALLOW_COPY_AND_ASSIGN(ScopedFrameBufferBinder);
+};
+
+// Encapsulates an OpenGL texture.
+class Texture {
+ public:
+  explicit Texture(GLES2DecoderImpl* decoder);
+  ~Texture();
+
+  // Create a new render texture.
+  void Create();
+
+  // Set the initial size and format of a render texture or resize it.
+  bool AllocateStorage(const gfx::Size& size);
+
+  // Copy the contents of the currently bound frame buffer.
+  void Copy(const gfx::Size& size);
+
+  // Destroy the render texture. This must be explicitly called before
+  // destroying this object.
+  void Destroy();
+
+  GLuint id() const {
+    return id_;
+  }
+
+ private:
+  GLES2DecoderImpl* decoder_;
+  GLuint id_;
+  DISALLOW_COPY_AND_ASSIGN(Texture);
+};
+
+// Encapsulates an OpenGL render buffer of any format.
+class RenderBuffer {
+ public:
+  explicit RenderBuffer(GLES2DecoderImpl* decoder);
+  ~RenderBuffer();
+
+  // Create a new render buffer.
+  void Create();
+
+  // Set the initial size and format of a render buffer or resize it.
+  bool AllocateStorage(const gfx::Size& size, GLenum format);
+
+  // Destroy the render buffer. This must be explicitly called before destroying
+  // this object.
+  void Destroy();
+
+  GLuint id() const {
+    return id_;
+  }
+
+ private:
+  GLES2DecoderImpl* decoder_;
+  GLuint id_;
+  DISALLOW_COPY_AND_ASSIGN(RenderBuffer);
+};
+
+// Encapsulates an OpenGL frame buffer.
+class FrameBuffer {
+ public:
+  explicit FrameBuffer(GLES2DecoderImpl* decoder);
+  ~FrameBuffer();
+
+  // Create a new frame buffer.
+  void Create();
+
+  // Attach a color render buffer to a frame buffer.
+  void AttachRenderTexture(Texture* texture);
+
+  // Attach a depth stencil render buffer to a frame buffer. Note that
+  // this unbinds any currently bound frame buffer.
+  void AttachDepthStencilRenderBuffer(RenderBuffer* render_buffer);
+
+  // Clear the given attached buffers.
+  void Clear(GLbitfield buffers);
+
+  // Destroy the frame buffer. This must be explicitly called before destroying
+  // this object.
+  void Destroy();
+
+  // See glCheckFramebufferStatusEXT.
+  GLenum CheckStatus();
+
+  GLuint id() const {
+    return id_;
+  }
+
+ private:
+  GLES2DecoderImpl* decoder_;
+  GLuint id_;
+  DISALLOW_COPY_AND_ASSIGN(FrameBuffer);
+};
 // }  // anonymous namespace.
 
 GLES2Decoder::GLES2Decoder(ContextGroup* group)
@@ -129,7 +274,8 @@
 
 // This class implements GLES2Decoder so we don't have to expose all the GLES2
 // cmd stuff to outside this class.
-class GLES2DecoderImpl : public GLES2Decoder {
+class GLES2DecoderImpl : public base::SupportsWeakPtr<GLES2DecoderImpl>,
+                         public GLES2Decoder {
  public:
   explicit GLES2DecoderImpl(ContextGroup* group);
 
@@ -210,8 +356,11 @@
   virtual const char* GetCommandName(unsigned int command_id) const;
 
   // Overridden from GLES2Decoder.
-  virtual bool Initialize();
+  virtual bool Initialize(GLES2Decoder* parent,
+                          const gfx::Size& size,
+                          uint32 parent_client_texture_id);
   virtual void Destroy();
+  virtual void ResizeOffscreenFrameBuffer(const gfx::Size& size);
   virtual bool MakeCurrent();
   virtual uint32 GetServiceIdForTesting(uint32 client_id);
   virtual GLES2Util* GetGLES2Util() { return &util_; }
@@ -239,6 +388,13 @@
   virtual void SetSwapBuffersCallback(Callback0::Type* callback);
 
  private:
+  friend class ScopedGLErrorSuppressor;
+  friend class ScopedTexture2DBinder;
+  friend class ScopedFrameBufferBinder;
+  friend class ScopedRenderBufferBinder;
+  friend class RenderBuffer;
+  friend class FrameBuffer;
+
   // State associated with each texture unit.
   struct TextureUnit {
     TextureUnit() : bind_target(GL_TEXTURE_2D) { }
@@ -309,6 +465,8 @@
   static bool InitGlew();
   void DestroyPlatformSpecific();
 
+  bool UpdateOffscreenFrameBufferSize();
+
   // Template to help call glGenXXX functions.
   template <void gl_gen_function(GLES2DecoderImpl*, GLsizei, GLuint*)>
   bool GenGLObjects(GLsizei n, const GLuint* client_ids) {
@@ -343,8 +501,8 @@
     GLsizei n, const GLuint* client_ids, GLuint* service_ids);
 
   // Creates a TextureInfo for the given texture.
-  void CreateTextureInfo(GLuint texture) {
-    texture_manager()->CreateTextureInfo(texture);
+  TextureManager::TextureInfo* CreateTextureInfo(GLuint texture) {
+    return texture_manager()->CreateTextureInfo(texture);
   }
 
   // Gets the texture info for the given texture. Returns NULL if none exists.
@@ -561,9 +719,6 @@
   void DoRenderbufferStorage(
     GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
 
-  // Swaps the buffers (copies/renders to the current window).
-  void DoSwapBuffers();
-
   // Wrappers for glTexParameter functions.
   void DoTexParameterf(GLenum target, GLenum pname, GLfloat param);
   void DoTexParameteri(GLenum target, GLenum pname, GLint param);
@@ -590,6 +745,10 @@
   // command.
   void CopyRealGLErrorsToWrapper();
 
+  // Clear all real GL errors. This is to prevent the client from seeing any
+  // errors caused by GL calls that it was not responsible for issuing.
+  void ClearRealGLErrors();
+
   // Checks if the current program and vertex attributes are valid for drawing.
   bool IsDrawValid(GLuint max_vertex_accessed);
 
@@ -654,6 +813,17 @@
 
   #undef GLES2_CMD_OP
 
+  // A parent decoder can access this decoders saved offscreen frame buffer.
+  // The parent pointer is reset if the parent is destroyed.
+  base::WeakPtr<GLES2DecoderImpl> parent_;
+
+  // Width and height to which an offscreen frame buffer should be resized on
+  // the next call to SwapBuffers.
+  gfx::Size pending_size_;
+
+  // Width and height of a decoder that renders to an offscreen frame buffer.
+  gfx::Size current_size_;
+
   // Current GL error bits.
   uint32 error_bits_;
 
@@ -714,11 +884,252 @@
 
   bool anti_aliased_;
 
+  // The offscreen frame buffer that the client renders to.
+  scoped_ptr<FrameBuffer> offscreen_target_frame_buffer_;
+  scoped_ptr<Texture> offscreen_target_color_texture_;
+  scoped_ptr<RenderBuffer> offscreen_target_depth_stencil_render_buffer_;
+
+  // The copy that is saved when SwapBuffers is called.
+  scoped_ptr<Texture> offscreen_saved_color_texture_;
+
+  // A frame buffer used for rendering to render textures and render buffers
+  // without concern about any state the client might have changed on the frame
+  // buffers it has access to.
+  scoped_ptr<FrameBuffer> temporary_frame_buffer_;
+
   scoped_ptr<Callback0::Type> swap_buffers_callback_;
 
   DISALLOW_COPY_AND_ASSIGN(GLES2DecoderImpl);
 };
 
+ScopedGLErrorSuppressor::ScopedGLErrorSuppressor(GLES2DecoderImpl* decoder)
+    : decoder_(decoder) {
+  decoder_->CopyRealGLErrorsToWrapper();
+}
+
+ScopedGLErrorSuppressor::~ScopedGLErrorSuppressor() {
+  decoder_->ClearRealGLErrors();
+}
+
+ScopedTexture2DBinder::ScopedTexture2DBinder(GLES2DecoderImpl* decoder,
+                                             GLuint id)
+    : decoder_(decoder) {
+  ScopedGLErrorSuppressor suppressor(decoder_);
+
+  // TODO(apatrick): Check if there are any other states that need to be reset
+  // before binding a new texture.
+  glActiveTexture(GL_TEXTURE0);
+  glBindTexture(GL_TEXTURE_2D, id);
+}
+
+ScopedTexture2DBinder::~ScopedTexture2DBinder() {
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  GLES2DecoderImpl::TextureUnit& info = decoder_->texture_units_[0];
+  GLuint last_id;
+  if (info.bound_texture_2d)
+    last_id = info.bound_texture_2d->texture_id();
+  else
+    last_id = 0;
+
+  glBindTexture(GL_TEXTURE_2D, last_id);
+  glActiveTexture(GL_TEXTURE0 + decoder_->active_texture_unit_);
+}
+
+ScopedRenderBufferBinder::ScopedRenderBufferBinder(GLES2DecoderImpl* decoder,
+                                                   GLuint id)
+    : decoder_(decoder) {
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  glBindRenderbufferEXT(GL_RENDERBUFFER, id);
+}
+
+ScopedRenderBufferBinder::~ScopedRenderBufferBinder() {
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  glBindRenderbufferEXT(GL_RENDERBUFFER, decoder_->bound_renderbuffer_);
+}
+
+ScopedFrameBufferBinder::ScopedFrameBufferBinder(GLES2DecoderImpl* decoder,
+                                                 GLuint id)
+    : decoder_(decoder) {
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  glBindFramebufferEXT(GL_FRAMEBUFFER, id);
+}
+
+ScopedFrameBufferBinder::~ScopedFrameBufferBinder() {
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  if (decoder_->bound_framebuffer_ == 0 &&
+      decoder_->offscreen_target_frame_buffer_.get()) {
+    glBindFramebufferEXT(GL_FRAMEBUFFER,
+                         decoder_->offscreen_target_frame_buffer_->id());
+  } else {
+    glBindFramebufferEXT(GL_FRAMEBUFFER, decoder_->bound_framebuffer_);
+  }
+}
+
+Texture::Texture(GLES2DecoderImpl* decoder)
+    : decoder_(decoder),
+      id_(0) {
+}
+
+Texture::~Texture() {
+  // This does not destroy the render texture because that would require that
+  // the associated GL context was current. Just check that it was explicitly
+  // destroyed.
+  DCHECK_EQ(id_, 0u);
+}
+
+void Texture::Create() {
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  Destroy();
+  glGenTextures(1, &id_);
+}
+
+bool Texture::AllocateStorage(const gfx::Size& size) {
+  DCHECK_NE(id_, 0u);
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  ScopedTexture2DBinder binder(decoder_, id_);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+  glTexParameteri(
+      GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+  glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
+
+  glTexImage2D(GL_TEXTURE_2D,
+               0,  // mip level
+               GL_RGBA,
+               size.width(),
+               size.height(),
+               0,  // border
+               GL_RGBA,
+               GL_UNSIGNED_BYTE,
+               NULL);
+
+  return glGetError() == GL_NO_ERROR;
+}
+
+void Texture::Copy(const gfx::Size& size) {
+  DCHECK_NE(id_, 0u);
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  ScopedTexture2DBinder binder(decoder_, id_);
+  glCopyTexImage2D(GL_TEXTURE_2D,
+                   0,  // level
+                   GL_RGBA,
+                   0, 0,
+                   size.width(),
+                   size.height(),
+                   0);  // border
+}
+
+void Texture::Destroy() {
+  if (id_ != 0) {
+    ScopedGLErrorSuppressor suppressor(decoder_);
+    glDeleteTextures(1, &id_);
+    id_ = 0;
+  }
+}
+
+RenderBuffer::RenderBuffer(GLES2DecoderImpl* decoder)
+    : decoder_(decoder),
+      id_(0) {
+}
+
+RenderBuffer::~RenderBuffer() {
+  // This does not destroy the render buffer because that would require that
+  // the associated GL context was current. Just check that it was explicitly
+  // destroyed.
+  DCHECK_EQ(id_, 0u);
+}
+
+void RenderBuffer::Create() {
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  Destroy();
+  glGenRenderbuffersEXT(1, &id_);
+}
+
+bool RenderBuffer::AllocateStorage(const gfx::Size& size, GLenum format) {
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  ScopedRenderBufferBinder binder(decoder_, id_);
+  glRenderbufferStorageEXT(GL_RENDERBUFFER,
+                           format,
+                           size.width(),
+                           size.height());
+  return glGetError() == GL_NO_ERROR;
+}
+
+void RenderBuffer::Destroy() {
+  if (id_ != 0) {
+    ScopedGLErrorSuppressor suppressor(decoder_);
+    glDeleteRenderbuffersEXT(1, &id_);
+    id_ = 0;
+  }
+}
+
+FrameBuffer::FrameBuffer(GLES2DecoderImpl* decoder)
+    : decoder_(decoder),
+      id_(0) {
+}
+
+FrameBuffer::~FrameBuffer() {
+  // This does not destroy the frame buffer because that would require that
+  // the associated GL context was current. Just check that it was explicitly
+  // destroyed.
+  DCHECK_EQ(id_, 0u);
+}
+
+void FrameBuffer::Create() {
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  Destroy();
+  glGenFramebuffersEXT(1, &id_);
+}
+
+void FrameBuffer::AttachRenderTexture(Texture* texture) {
+  DCHECK_NE(id_, 0u);
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  ScopedFrameBufferBinder binder(decoder_, id_);
+  GLuint attach_id = texture ? texture->id() : 0;
+  glFramebufferTexture2DEXT(GL_FRAMEBUFFER,
+                            GL_COLOR_ATTACHMENT0,
+                            GL_TEXTURE_2D,
+                            attach_id,
+                            0);
+}
+
+void FrameBuffer::AttachDepthStencilRenderBuffer(RenderBuffer* render_buffer) {
+  DCHECK_NE(id_, 0u);
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  ScopedFrameBufferBinder binder(decoder_, id_);
+  GLuint attach_id = render_buffer ? render_buffer->id() : 0;
+  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER,
+                               GL_DEPTH_ATTACHMENT,
+                               GL_RENDERBUFFER,
+                               attach_id);
+  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER,
+                               GL_STENCIL_ATTACHMENT,
+                               GL_RENDERBUFFER,
+                               attach_id);
+}
+
+void FrameBuffer::Clear(GLbitfield buffers) {
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  ScopedFrameBufferBinder binder(decoder_, id_);
+  glClear(buffers);
+}
+
+void FrameBuffer::Destroy() {
+  if (id_ != 0) {
+    ScopedGLErrorSuppressor suppressor(decoder_);
+    glDeleteFramebuffersEXT(1, &id_);
+    id_ = 0;
+  }
+}
+
+GLenum FrameBuffer::CheckStatus() {
+  DCHECK_NE(id_, 0u);
+  ScopedGLErrorSuppressor suppressor(decoder_);
+  ScopedFrameBufferBinder binder(decoder_, id_);
+  return glCheckFramebufferStatusEXT(GL_FRAMEBUFFER);
+}
+
 GLES2Decoder* GLES2Decoder::Create(ContextGroup* group) {
   return new GLES2DecoderImpl(group);
 }
@@ -750,11 +1161,21 @@
       anti_aliased_(false) {
 }
 
-bool GLES2DecoderImpl::Initialize() {
+bool GLES2DecoderImpl::Initialize(GLES2Decoder* parent,
+                                  const gfx::Size& size,
+                                  uint32 parent_client_texture_id) {
+  // Keep only a weak pointer to the parent so we don't unmap its client
+  // frame buffer is after it has been destroyed.
+  if (parent)
+    parent_ = static_cast<GLES2DecoderImpl*>(parent)->AsWeakPtr();
+
+  pending_size_ = size;
+
   if (!InitPlatformSpecific()) {
     Destroy();
     return false;
   }
+
   if (!MakeCurrent()) {
     Destroy();
     return false;
@@ -806,6 +1227,52 @@
   glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
   CHECK_GL_ERROR();
 
+  if (size.width() > 0 && size.height() > 0) {
+    // Create the target frame buffer. This is the one that the client renders
+    // directly to.
+    offscreen_target_frame_buffer_.reset(new FrameBuffer(this));
+    offscreen_target_frame_buffer_->Create();
+    offscreen_target_color_texture_.reset(new Texture(this));
+    offscreen_target_color_texture_->Create();
+    offscreen_target_depth_stencil_render_buffer_.reset(
+        new RenderBuffer(this));
+    offscreen_target_depth_stencil_render_buffer_->Create();
+
+    // Create the saved offscreen texture. The target frame buffer is copied
+    // here when SwapBuffers is called.
+    offscreen_saved_color_texture_.reset(new Texture(this));
+    offscreen_saved_color_texture_->Create();
+
+    // Create the temporary frame buffer, used to operate on render textures
+    // without concern for state the client might have changed on the frame
+    // buffers it has access to, like the clear color and the color mask.
+    temporary_frame_buffer_.reset(new FrameBuffer(this));
+    temporary_frame_buffer_->Create();
+
+    // Map the ID of the saved offscreen texture into the parent so that
+    // it can reference it.
+    if (parent_) {
+      GLuint service_id = offscreen_saved_color_texture_->id();
+      parent_->id_manager()->AddMapping(parent_client_texture_id,
+                                        service_id);
+      TextureManager::TextureInfo* info =
+          parent_->CreateTextureInfo(service_id);
+      parent_->texture_manager()->SetInfoTarget(info, GL_TEXTURE_2D);
+    }
+
+    // Allocate the render buffers at their initial size and check the status
+    // of the frame buffers is okay.
+    if (!UpdateOffscreenFrameBufferSize()) {
+      DLOG(ERROR) << "Could not allocate offscreen buffer storage.";
+      Destroy();
+      return false;
+    }
+
+    // Bind to the new default frame buffer (the offscreen target frame buffer).
+    // This should now be associated with ID zero.
+    DoBindFramebuffer(GL_FRAMEBUFFER, 0);
+  }
+
   return true;
 }
 
@@ -919,7 +1386,7 @@
     // GL context was successfully created and applied to the window's DC.
     // Startup GLEW, the GL extensions wrangler.
     if (InitGlew()) {
-      DLOG(INFO) << "Initialized GLEW " << ::glewGetString(GLEW_VERSION);
+      DLOG(INFO) << "Initialized GLEW " << glewGetString(GLEW_VERSION);
     } else {
       ::wglMakeCurrent(intermediate_dc, NULL);
       ::wglDeleteContext(gl_context);
@@ -1124,6 +1591,7 @@
 }
 
 bool GLES2DecoderImpl::InitPlatformSpecific() {
+  bool offscreen = pending_size_.width() > 0 && pending_size_.height() > 0;
 #if defined(UNIT_TEST)
 #elif defined(GLES2_GPU_SERVICE_BACKEND_NATIVE_GLES2)
 #elif defined(OS_WIN)
@@ -1132,22 +1600,13 @@
   if (!success)
     return false;
 
-  if (hwnd()) {
-    // The GL context will render to this window.
-    gl_device_context_ = ::GetDC(hwnd());
-
-    if (!::SetPixelFormat(gl_device_context_,
-                          pixel_format_,
-                          &kPixelFormatDescriptor)) {
-      DLOG(ERROR) << "Unable to set the pixel format for GL context.";
-      DestroyPlatformSpecific();
-      return false;
-    }
-  } else {
+  if (offscreen) {
     // Create a device context compatible with the primary display.
     HDC display_device_context = ::CreateDC(L"DISPLAY", NULL, NULL, NULL);
 
-    // Create a 1 x 1 pbuffer suitable for use with the device.
+    // Create a 1 x 1 pbuffer suitable for use with the device. This is just
+    // a stepping stone towards creating a frame buffer object. It doesn't
+    // matter what size it is.
     const int kNoAttributes[] = { 0 };
     pbuffer_ = ::wglCreatePbufferARB(display_device_context,
                                      pixel_format_,
@@ -1166,6 +1625,17 @@
       DestroyPlatformSpecific();
       return false;
     }
+  } else {
+    // The GL context will render to this window.
+    gl_device_context_ = ::GetDC(hwnd());
+
+    if (!::SetPixelFormat(gl_device_context_,
+                          pixel_format_,
+                          &kPixelFormatDescriptor)) {
+      DLOG(ERROR) << "Unable to set the pixel format for GL context.";
+      DestroyPlatformSpecific();
+      return false;
+    }
   }
 
   gl_context_ = ::wglCreateContext(gl_device_context_);
@@ -1174,11 +1644,32 @@
     DestroyPlatformSpecific();
     return false;
   }
+
+  if (parent_) {
+    if (!wglShareLists(parent_->gl_context_, gl_context_)) {
+      DLOG(ERROR) << "Could not share GL contexts.";
+      DestroyPlatformSpecific();
+      return false;
+    }
+  }
+
 #elif defined(OS_LINUX)
+  // TODO(apatrick): offscreen rendering not yet supported on this platform.
+  DCHECK(!offscreen);
+
+  // TODO(apatrick): parent contexts not yet supported on this platform.
+  DCHECK(!parent_);
+
   DCHECK(window());
   if (!window()->Initialize())
     return false;
 #elif defined(OS_MACOSX)
+  // TODO(apatrick): offscreen rendering not yet supported on this platform.
+  DCHECK(!offscreen);
+
+  // TODO(apatrick): parent contexts not yet supported on this platform.
+  DCHECK(!parent_);
+
   return surface_.Initialize();
 #endif
 
@@ -1192,7 +1683,7 @@
   GLenum glew_error = glewInit();
   if (glew_error != GLEW_OK) {
     DLOG(ERROR) << "Unable to initialise GLEW : "
-                << ::glewGetErrorString(glew_error);
+                << glewGetErrorString(glew_error);
     return false;
   }
 
@@ -1265,6 +1756,85 @@
 #endif
 }
 
+bool GLES2DecoderImpl::UpdateOffscreenFrameBufferSize() {
+  if (current_size_ != pending_size_)
+    return true;
+
+  // Reallocate the offscreen target buffers.
+  if (!offscreen_target_color_texture_->AllocateStorage(pending_size_)) {
+    return false;
+  }
+
+  if (!offscreen_target_depth_stencil_render_buffer_->AllocateStorage(
+      pending_size_, GL_DEPTH24_STENCIL8)) {
+    return false;
+  }
+
+  // Attach the offscreen target buffers to the temporary frame buffer
+  // so they can be cleared using that frame buffer's clear parameters (all
+  // zero, no color mask, etc).
+  temporary_frame_buffer_->AttachRenderTexture(
+      offscreen_target_color_texture_.get());
+  temporary_frame_buffer_->AttachDepthStencilRenderBuffer(
+      offscreen_target_depth_stencil_render_buffer_.get());
+  if (temporary_frame_buffer_->CheckStatus() !=
+      GL_FRAMEBUFFER_COMPLETE) {
+    return false;
+  }
+
+  // Clear the offscreen target buffers to all zero (using the saved frame
+  // buffer they are temporarily attached to).
+  temporary_frame_buffer_->Clear(
+      GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+  // Detach the offscreen target buffer.
+  temporary_frame_buffer_->AttachRenderTexture(NULL);
+  temporary_frame_buffer_->AttachDepthStencilRenderBuffer(NULL);
+
+  // Attach the offscreen target buffers to the proper frame buffer.
+  offscreen_target_frame_buffer_->AttachRenderTexture(
+      offscreen_target_color_texture_.get());
+  offscreen_target_frame_buffer_->AttachDepthStencilRenderBuffer(
+      offscreen_target_depth_stencil_render_buffer_.get());
+  if (offscreen_target_frame_buffer_->CheckStatus() !=
+      GL_FRAMEBUFFER_COMPLETE) {
+    return false;
+  }
+
+  // Create the saved offscreen color texture.
+  offscreen_saved_color_texture_->AllocateStorage(pending_size_);
+
+  // Clear the offscreen saved color texture by copying the cleared target
+  // frame buffer into it.
+  {
+    ScopedFrameBufferBinder binder(this, offscreen_target_frame_buffer_->id());
+    offscreen_saved_color_texture_->Copy(pending_size_);
+  }
+
+  // Update the info about the offscreen saved color texture in the parent.
+  // The reference to the parent is a weak pointer and will become null if the
+  // parent is later destroyed.
+  if (parent_) {
+    GLuint service_id = offscreen_saved_color_texture_->id();
+
+    TextureManager::TextureInfo* info =
+        parent_->texture_manager()->GetTextureInfo(service_id);
+    DCHECK(info);
+
+    info->SetLevelInfo(GL_TEXTURE_2D,
+                       0,  // level
+                       GL_RGBA,
+                       pending_size_.width(), pending_size_.height(),
+                       1,  // depth
+                       0,  // border
+                       GL_RGBA,
+                       GL_UNSIGNED_BYTE);
+  }
+
+  current_size_ = pending_size_;
+  return true;
+}
+
 #if defined(OS_MACOSX)
 
 uint64 GLES2DecoderImpl::SetWindowSizeForIOSurface(int32 width, int32 height) {
@@ -1300,6 +1870,37 @@
 }
 
 void GLES2DecoderImpl::Destroy() {
+  MakeCurrent();
+
+  // Remove the saved frame buffer mapping from the parent decoder. The
+  // parent pointer is a weak pointer so it will be null if the parent has
+  // already been destroyed.
+  if (parent_) {
+    // First check the texture has been mapped into the parent. This might not
+    // be the case if initialization failed midway through.
+    GLuint service_id = offscreen_saved_color_texture_->id();
+    GLuint client_id;
+    if (parent_->id_manager()->GetClientId(service_id, &client_id)) {
+      parent_->texture_manager()->RemoveTextureInfo(service_id);
+      parent_->id_manager()->RemoveMapping(client_id, service_id);
+    }
+  }
+
+  if (offscreen_target_frame_buffer_.get())
+    offscreen_target_frame_buffer_->Destroy();
+
+  if (offscreen_target_color_texture_.get())
+    offscreen_target_color_texture_->Destroy();
+
+  if (offscreen_target_depth_stencil_render_buffer_.get())
+    offscreen_target_depth_stencil_render_buffer_->Destroy();
+
+  if (temporary_frame_buffer_.get())
+    temporary_frame_buffer_->Destroy();
+
+  if (offscreen_saved_color_texture_.get())
+    offscreen_saved_color_texture_->Destroy();
+
 #if defined(UNIT_TEST)
 #elif defined(GLES2_GPU_SERVICE_BACKEND_NATIVE_GLES2)
 #elif defined(OS_LINUX)
@@ -1312,6 +1913,13 @@
   DestroyPlatformSpecific();
 }
 
+void GLES2DecoderImpl::ResizeOffscreenFrameBuffer(const gfx::Size& size) {
+  // We can't resize the render buffers immediately because there might be a
+  // partial frame rendered into them and we don't want the tail end of that
+  // rendered into the reallocated storage. Defer until the next SwapBuffers.
+  pending_size_ = size;
+}
+
 const char* GLES2DecoderImpl::GetCommandName(unsigned int command_id) const {
   if (command_id > kStartPoint && command_id < kNumCommands) {
     return gles2::GetCommandName(static_cast<CommandId>(command_id));
@@ -1452,6 +2060,12 @@
 
 void GLES2DecoderImpl::DoBindFramebuffer(GLenum target, GLuint framebuffer) {
   bound_framebuffer_ = framebuffer;
+
+  // When rendering to an offscreen frame buffer, instead of unbinding from
+  // the current frame buffer, bind to the offscreen target frame buffer.
+  if (framebuffer == 0 && offscreen_target_frame_buffer_.get())
+    framebuffer = offscreen_target_frame_buffer_->id();
+
   glBindFramebufferEXT(target, framebuffer);
 }
 
@@ -1685,24 +2299,6 @@
   }
 };
 
-void GLES2DecoderImpl::DoSwapBuffers() {
-#if defined(UNIT_TEST)
-#elif defined(GLES2_GPU_SERVICE_BACKEND_NATIVE_GLES2)
-#elif defined(OS_WIN)
-  ::SwapBuffers(gl_device_context_);
-#elif defined(OS_LINUX)
-  DCHECK(window());
-  window()->SwapBuffers();
-#elif defined(OS_MACOSX)
-  // TODO(kbr): Need to property hook up and track the OpenGL state and hook
-  // up the notion of the currently bound FBO.
-  surface_.SwapBuffers();
-#endif
-  if (swap_buffers_callback_.get()) {
-    swap_buffers_callback_->Run();
-  }
-}
-
 void GLES2DecoderImpl::DoTexParameterf(
     GLenum target, GLenum pname, GLfloat param) {
   TextureManager::TextureInfo* info = GetTextureInfoForTarget(target);
@@ -1812,6 +2408,13 @@
   }
 }
 
+void GLES2DecoderImpl::ClearRealGLErrors() {
+  GLenum error;
+  while ((error = glGetError()) != GL_NO_ERROR) {
+    NOTREACHED() << "GL error " << error << " was unhandled.";
+  }
+}
+
 bool GLES2DecoderImpl::VertexAttribInfo::CanAccess(GLuint index) {
   if (!enabled_) {
     return true;
@@ -2981,6 +3584,48 @@
   return error::kNoError;
 }
 
+error::Error GLES2DecoderImpl::HandleSwapBuffers(
+    uint32 immediate_data_size, const gles2::SwapBuffers& c) {
+  // Check a client created frame buffer is not bound. TODO(apatrick):
+  // this error is overkill. It will require that the client recreate the
+  // context to continue.
+  if (bound_framebuffer_ != 0)
+    return error::kLostContext;
+
+  // If offscreen then don't actually SwapBuffers to the display. Just copy
+  // the rendered frame to another frame buffer.
+  if (offscreen_target_frame_buffer_.get()) {
+    ScopedGLErrorSuppressor suppressor(this);
+
+    // First check to see if a deferred offscreen render buffer resize is
+    // pending.
+    if (!UpdateOffscreenFrameBufferSize())
+      return error::kLostContext;
+
+    ScopedFrameBufferBinder binder(this, offscreen_target_frame_buffer_->id());
+    offscreen_saved_color_texture_->Copy(current_size_);
+  } else {
+#if defined(UNIT_TEST)
+#elif defined(GLES2_GPU_SERVICE_BACKEND_NATIVE_GLES2)
+#elif defined(OS_WIN)
+    ::SwapBuffers(gl_device_context_);
+#elif defined(OS_LINUX)
+    DCHECK(window());
+    window()->SwapBuffers();
+#elif defined(OS_MACOSX)
+    // TODO(kbr): Need to property hook up and track the OpenGL state and hook
+    // up the notion of the currently bound FBO.
+    surface_.SwapBuffers();
+#endif
+  }
+
+  if (swap_buffers_callback_.get()) {
+    swap_buffers_callback_->Run();
+  }
+
+  return error::kNoError;
+}
+
 // Include the auto-generated part of this file. We split this because it means
 // we can easily edit the non-auto generated parts right here in this file
 // instead of having to edit some template or the code generator.
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.h b/gpu/command_buffer/service/gles2_cmd_decoder.h
index 26c6239d..58e1193e 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.h
@@ -15,6 +15,8 @@
 #if defined(OS_MACOSX)
 #include "app/surface/transport_dib.h"
 #endif
+
+#include "gfx/size.h"
 #include "gpu/command_buffer/service/common_decoder.h"
 
 
@@ -71,14 +73,20 @@
       Callback1<TransportDIB::Id>::Type* deallocator) = 0;
 #endif
 
-  // Initializes the graphics context.
+  // Initializes the graphics context. Can create an offscreen
+  // decoder with a frame buffer that can be referenced from the parent.
   // Returns:
   //   true if successful.
-  virtual bool Initialize() = 0;
+  virtual bool Initialize(GLES2Decoder* parent,
+                          const gfx::Size& size,
+                          uint32 parent_texture_id) = 0;
 
   // Destroys the graphics context.
   virtual void Destroy() = 0;
 
+  // Resize an offscreen frame buffer.
+  virtual void ResizeOffscreenFrameBuffer(const gfx::Size& size) = 0;
+
   // Make this decoder's GL context current.
   virtual bool MakeCurrent() = 0;
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h b/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h
index cb534a4..7507d95 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_autogen.h
@@ -2842,11 +2842,5 @@
   return error::kNoError;
 }
 
-error::Error GLES2DecoderImpl::HandleSwapBuffers(
-    uint32 immediate_data_size, const gles2::SwapBuffers& c) {
-  DoSwapBuffers();
-  return error::kNoError;
-}
-
 #endif  // GPU_COMMAND_BUFFER_SERVICE_GLES2_CMD_DECODER_AUTOGEN_H_
 
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_mock.h b/gpu/command_buffer/service/gles2_cmd_decoder_mock.h
index 2ef2d2d..f94eb7d 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_mock.h
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_mock.h
@@ -7,6 +7,7 @@
 #ifndef GPU_COMMAND_BUFFER_SERVICE_GLES2_CMD_DECODER_MOCK_H_
 #define GPU_COMMAND_BUFFER_SERVICE_GLES2_CMD_DECODER_MOCK_H_
 
+#include "gfx/size.h"
 #include "gpu/command_buffer/service/gles2_cmd_decoder.h"
 #include "base/callback.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -28,8 +29,11 @@
 #if defined(OS_MACOSX)
   MOCK_METHOD2(SetWindowSize, uint64(int32 width, int32 height));
 #endif
-  MOCK_METHOD0(Initialize, bool());
+  MOCK_METHOD3(Initialize, bool(GLES2Decoder* parent,
+                                const gfx::Size& size,
+                                uint32 parent_texture_id));
   MOCK_METHOD0(Destroy, void());
+  MOCK_METHOD1(ResizeOffscreenFrameBuffer, void(const gfx::Size& size));
   MOCK_METHOD0(MakeCurrent, bool());
   MOCK_METHOD1(GetServiceIdForTesting, uint32(uint32 client_id));
   MOCK_METHOD0(GetGLES2Util, GLES2Util*());
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
index 0bb32c68..10acd1b 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder_unittest_base.cc
@@ -92,7 +92,7 @@
   shared_memory_id_ = kSharedMemoryId;
 
   decoder_.reset(GLES2Decoder::Create(&group_));
-  decoder_->Initialize();
+  decoder_->Initialize(NULL, gfx::Size(), 0);
   decoder_->set_engine(engine_.get());
 
   EXPECT_CALL(*gl_, GenBuffersARB(_, _))
diff --git a/gpu/command_buffer/service/gpu_processor.cc b/gpu/command_buffer/service/gpu_processor.cc
index 7a748e7f..d08069c 100644
--- a/gpu/command_buffer/service/gpu_processor.cc
+++ b/gpu/command_buffer/service/gpu_processor.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/callback.h"
+#include "base/compiler_specific.h"
 #include "base/message_loop.h"
 #include "gpu/command_buffer/service/gpu_processor.h"
 
@@ -12,7 +13,8 @@
 
 GPUProcessor::GPUProcessor(CommandBuffer* command_buffer)
     : command_buffer_(command_buffer),
-      commands_per_update_(100) {
+      commands_per_update_(100),
+      method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
   DCHECK(command_buffer);
   decoder_.reset(gles2::GLES2Decoder::Create(&group_));
   decoder_->set_engine(this);
@@ -23,13 +25,15 @@
                            CommandParser* parser,
                            int commands_per_update)
     : command_buffer_(command_buffer),
-      commands_per_update_(commands_per_update) {
+      commands_per_update_(commands_per_update),
+      method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
   DCHECK(command_buffer);
   decoder_.reset(decoder);
   parser_.reset(parser);
 }
 
 GPUProcessor::~GPUProcessor() {
+  Destroy();
 }
 
 void GPUProcessor::ProcessCommands() {
@@ -59,7 +63,8 @@
 
   if (!parser_->IsEmpty()) {
     MessageLoop::current()->PostTask(
-        FROM_HERE, NewRunnableMethod(this, &GPUProcessor::ProcessCommands));
+        FROM_HERE,
+        method_factory_.NewRunnableMethod(&GPUProcessor::ProcessCommands));
   }
 }
 
@@ -83,6 +88,10 @@
   return parser_->get();
 }
 
+void GPUProcessor::ResizeOffscreenFrameBuffer(const gfx::Size& size) {
+  decoder_->ResizeOffscreenFrameBuffer(size);
+}
+
 #if defined(OS_MACOSX)
 uint64 GPUProcessor::SetWindowSizeForIOSurface(int32 width, int32 height) {
   return decoder_->SetWindowSizeForIOSurface(width, height);
diff --git a/gpu/command_buffer/service/gpu_processor.h b/gpu/command_buffer/service/gpu_processor.h
index d34e35d..d61bd60 100644
--- a/gpu/command_buffer/service/gpu_processor.h
+++ b/gpu/command_buffer/service/gpu_processor.h
@@ -9,7 +9,9 @@
 #include "base/ref_counted.h"
 #include "base/scoped_ptr.h"
 #include "base/shared_memory.h"
+#include "base/task.h"
 #include "gfx/native_widget_types.h"
+#include "gfx/size.h"
 #include "gpu/command_buffer/common/command_buffer.h"
 #include "gpu/command_buffer/service/cmd_buffer_engine.h"
 #include "gpu/command_buffer/service/cmd_parser.h"
@@ -20,8 +22,7 @@
 
 // This class processes commands in a command buffer. It is event driven and
 // posts tasks to the current message loop to do additional work.
-class GPUProcessor : public base::RefCounted<GPUProcessor>,
-                     public CommandBufferEngine {
+class GPUProcessor : public CommandBufferEngine {
  public:
   explicit GPUProcessor(CommandBuffer* command_buffer);
 
@@ -31,7 +32,10 @@
                CommandParser* parser,
                int commands_per_update);
 
-  virtual bool Initialize(gfx::PluginWindowHandle hwnd);
+  virtual bool Initialize(gfx::PluginWindowHandle hwnd,
+                          GPUProcessor* parent,
+                          const gfx::Size& size,
+                          uint32 parent_texture_id);
 
   virtual ~GPUProcessor();
 
@@ -45,6 +49,9 @@
   virtual bool SetGetOffset(int32 offset);
   virtual int32 GetGetOffset();
 
+  // Asynchronously resizes an offscreen frame buffer.
+  void ResizeOffscreenFrameBuffer(const gfx::Size& size);
+
 #if defined(OS_MACOSX)
   // Needed only on Mac OS X, which does not render into an on-screen
   // window and therefore requires the backing store to be resized
@@ -71,37 +78,15 @@
   // through the ProcessCommands callback.
   CommandBuffer* command_buffer_;
 
-  scoped_ptr< ::base::SharedMemory> mapped_ring_buffer_;
   int commands_per_update_;
 
   gles2::ContextGroup group_;
   scoped_ptr<gles2::GLES2Decoder> decoder_;
   scoped_ptr<CommandParser> parser_;
+
+  ScopedRunnableMethodFactory<GPUProcessor> method_factory_;
 };
 
 }  // namespace gpu
 
-// Callbacks to the GPUProcessor hold a reference count.
-template <typename Method>
-class CallbackStorage<gpu::GPUProcessor, Method> {
- public:
-  CallbackStorage(gpu::GPUProcessor* obj, Method method)
-      : obj_(obj),
-        meth_(method) {
-    DCHECK(obj_);
-    obj_->AddRef();
-  }
-
-  ~CallbackStorage() {
-    obj_->Release();
-  }
-
- protected:
-  gpu::GPUProcessor* obj_;
-  Method meth_;
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(CallbackStorage);
-};
-
 #endif  // GPU_COMMAND_BUFFER_SERVICE_GPU_PROCESSOR_H_
diff --git a/gpu/command_buffer/service/gpu_processor_linux.cc b/gpu/command_buffer/service/gpu_processor_linux.cc
index 205cceb..d4309335 100644
--- a/gpu/command_buffer/service/gpu_processor_linux.cc
+++ b/gpu/command_buffer/service/gpu_processor_linux.cc
@@ -10,7 +10,10 @@
 
 namespace gpu {
 
-bool GPUProcessor::Initialize(gfx::PluginWindowHandle handle) {
+bool GPUProcessor::Initialize(gfx::PluginWindowHandle handle,
+                              GPUProcessor* parent,
+                              const gfx::Size& size,
+                              uint32 parent_texture_id) {
   DCHECK(handle);
 
   // Cannot reinitialize.
@@ -34,17 +37,27 @@
   // Initialize GAPI immediately if the window handle is valid.
   XWindowWrapper *window = new XWindowWrapper(GDK_DISPLAY(), handle);
   decoder_->set_window_wrapper(window);
-  return decoder_->Initialize();
-}
+  gles2::GLES2Decoder* parent_decoder = parent ? parent->decoder_.get() : NULL;
+  if (!decoder_->Initialize(parent_decoder,
+                            size,
+                            parent_texture_id)) {
+    Destroy();
+    return false;
+  }
+
+  return true;}
 
 void GPUProcessor::Destroy() {
-  // Destroy GAPI if window handle has not already become invalid.
-  XWindowWrapper *window = decoder_->window();
-  if (window) {
+  // Destroy decoder if initialized.
+  if (decoder_.get()) {
+    XWindowWrapper *window = decoder_->window();
     decoder_->Destroy();
     decoder_->set_window_wrapper(NULL);
     delete window;
+    decoder_.reset();
   }
+
+  parser_.reset();
 }
 
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/gpu_processor_mac.cc b/gpu/command_buffer/service/gpu_processor_mac.cc
index a36560c..ef13fc6 100644
--- a/gpu/command_buffer/service/gpu_processor_mac.cc
+++ b/gpu/command_buffer/service/gpu_processor_mac.cc
@@ -8,7 +8,10 @@
 
 namespace gpu {
 
-bool GPUProcessor::Initialize(gfx::PluginWindowHandle handle) {
+bool GPUProcessor::Initialize(gfx::PluginWindowHandle handle,
+                              GPUProcessor* parent,
+                              const gfx::Size& size,
+                              uint32 parent_texture_id) {
   // At this level we do not need the PluginWindowHandle. It is only
   // needed at the CommandBufferStub level to identify which GPU
   // plugin instance is creating a new backing store in response to a
@@ -29,11 +32,25 @@
   }
 
   // Initialize GAPI.
-  return decoder_->Initialize();
+  gles2::GLES2Decoder* parent_decoder = parent ? parent->decoder_.get() : NULL;
+  if (!decoder_->Initialize(parent_decoder,
+                            size,
+                            parent_texture_id)) {
+    Destroy();
+    return false;
+  }
+
+  return true;
 }
 
 void GPUProcessor::Destroy() {
-  decoder_->Destroy();
+  // Destroy decoder if initialized.
+  if (decoder_.get()) {
+    decoder_->Destroy();
+    decoder_.reset();
+  }
+
+  parser_.reset();
 }
 }  // namespace gpu
 
diff --git a/gpu/command_buffer/service/gpu_processor_unittest.cc b/gpu/command_buffer/service/gpu_processor_unittest.cc
index 51cfe01..95be087 100644
--- a/gpu/command_buffer/service/gpu_processor_unittest.cc
+++ b/gpu/command_buffer/service/gpu_processor_unittest.cc
@@ -58,10 +58,10 @@
                                 0,
                                 async_api_.get());
 
-    processor_ = new GPUProcessor(command_buffer_.get(),
-                                  decoder_,
-                                  parser_,
-                                  2);
+    processor_.reset(new GPUProcessor(command_buffer_.get(),
+                                      decoder_,
+                                      parser_,
+                                      2));
   }
 
   virtual void TearDown() {
@@ -85,7 +85,7 @@
   gles2::MockGLES2Decoder* decoder_;
   CommandParser* parser_;
   scoped_ptr<AsyncAPIMock> async_api_;
-  scoped_refptr<GPUProcessor> processor_;
+  scoped_ptr<GPUProcessor> processor_;
 };
 
 TEST_F(GPUProcessorTest, ProcessorDoesNothingIfRingBufferIsEmpty) {
diff --git a/gpu/command_buffer/service/gpu_processor_win.cc b/gpu/command_buffer/service/gpu_processor_win.cc
index bce783b4a..537872c 100644
--- a/gpu/command_buffer/service/gpu_processor_win.cc
+++ b/gpu/command_buffer/service/gpu_processor_win.cc
@@ -10,7 +10,10 @@
 
 namespace gpu {
 
-bool GPUProcessor::Initialize(gfx::PluginWindowHandle handle) {
+bool GPUProcessor::Initialize(gfx::PluginWindowHandle handle,
+                              GPUProcessor* parent,
+                              const gfx::Size& size,
+                              uint32 parent_texture_id) {
   // Cannot reinitialize.
   if (parser_.get())
     return false;
@@ -31,14 +34,25 @@
 
   // Initialize GAPI immediately if the window handle is valid.
   decoder_->set_hwnd(handle);
-  return decoder_->Initialize();
+  gles2::GLES2Decoder* parent_decoder = parent ? parent->decoder_.get() : NULL;
+  if (!decoder_->Initialize(parent_decoder,
+                            size,
+                            parent_texture_id)) {
+    Destroy();
+    return false;
+  }
+
+  return true;
 }
 
 void GPUProcessor::Destroy() {
   // Destroy decoder if initialized.
-  if (parser_.get()) {
+  if (decoder_.get()) {
     decoder_->Destroy();
     decoder_->set_hwnd(NULL);
+    decoder_.reset();
   }
+
+  parser_.reset();
 }
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/texture_manager.cc b/gpu/command_buffer/service/texture_manager.cc
index d6a1dce..4369199a 100644
--- a/gpu/command_buffer/service/texture_manager.cc
+++ b/gpu/command_buffer/service/texture_manager.cc
@@ -265,12 +265,13 @@
                                               max_cube_map_texture_size)) {
 }
 
-void TextureManager::CreateTextureInfo(GLuint texture_id) {
+TextureManager::TextureInfo* TextureManager::CreateTextureInfo(
+    GLuint texture_id) {
+  TextureInfo::Ref info(new TextureInfo(texture_id));
   std::pair<TextureInfoMap::iterator, bool> result =
-      texture_infos_.insert(
-          std::make_pair(texture_id,
-                         TextureInfo::Ref(new TextureInfo(texture_id))));
+      texture_infos_.insert(std::make_pair(texture_id, info));
   DCHECK(result.second);
+  return info.get();
 }
 
 TextureManager::TextureInfo* TextureManager::GetTextureInfo(
diff --git a/gpu/command_buffer/service/texture_manager.h b/gpu/command_buffer/service/texture_manager.h
index 358cbbc..2dba85d 100644
--- a/gpu/command_buffer/service/texture_manager.h
+++ b/gpu/command_buffer/service/texture_manager.h
@@ -229,7 +229,7 @@
   }
 
   // Creates a new texture info.
-  void CreateTextureInfo(GLuint texture_id);
+  TextureInfo* CreateTextureInfo(GLuint texture_id);
 
   // Gets the texture info for the given texture.
   TextureInfo* GetTextureInfo(GLuint texture_id);
diff --git a/gpu/demos/framework/window.cc b/gpu/demos/framework/window.cc
index 600514f..0a9db79 100644
--- a/gpu/demos/framework/window.cc
+++ b/gpu/demos/framework/window.cc
@@ -50,20 +50,23 @@
   ::gles2::GetGLContext()->SwapBuffers();
 }
 
+// TODO(apatrick): It looks like all the resources allocated here leak. We
+// should fix that if we want to use this Window class for anything beyond this
+// simple use case.
 bool Window::CreateRenderContext(gfx::PluginWindowHandle hwnd) {
   scoped_ptr<CommandBufferService> command_buffer(new CommandBufferService);
   if (!command_buffer->Initialize(kCommandBufferSize)) {
     return false;
   }
 
-  scoped_refptr<GPUProcessor> gpu_processor(
+  GPUProcessor* gpu_processor(
       new GPUProcessor(command_buffer.get()));
-  if (!gpu_processor->Initialize(hwnd)) {
+  if (!gpu_processor->Initialize(hwnd, NULL, gfx::Size(), 0)) {
     return false;
   }
 
   command_buffer->SetPutOffsetChangeCallback(
-      NewCallback(gpu_processor.get(), &GPUProcessor::ProcessCommands));
+      NewCallback(gpu_processor, &GPUProcessor::ProcessCommands));
 
   GLES2CmdHelper* helper = new GLES2CmdHelper(command_buffer.get());
   if (!helper->Initialize()) {
diff --git a/gpu/gpu.gyp b/gpu/gpu.gyp
index 4e3106f..06818fc 100644
--- a/gpu/gpu.gyp
+++ b/gpu/gpu.gyp
@@ -247,6 +247,7 @@
       'dependencies': [
         'command_buffer_common',
         'gl_libs',
+        '../gfx/gfx.gyp:gfx',
       ],
       'sources': [
         'command_buffer/service/common_decoder.cc',