diff --git tests/cefclient/CMakeLists.txt.in tests/cefclient/CMakeLists.txt.in
index bf17d06b..2cd49c5b 100644
--- tests/cefclient/CMakeLists.txt.in
+++ tests/cefclient/CMakeLists.txt.in
@@ -248,7 +248,7 @@ if(OS_WINDOWS)
   add_executable(${CEF_TARGET} WIN32 ${CEFCLIENT_SRCS})
   SET_EXECUTABLE_TARGET_PROPERTIES(${CEF_TARGET})
   add_dependencies(${CEF_TARGET} libcef_dll_wrapper)
-  target_link_libraries(${CEF_TARGET} libcef_lib libcef_dll_wrapper ${CEF_STANDARD_LIBS} glu32.lib imm32.lib opengl32.lib)
+  target_link_libraries(${CEF_TARGET} libcef_lib libcef_dll_wrapper ${CEF_STANDARD_LIBS} d3d11.lib glu32.lib imm32.lib opengl32.lib)
 
   if(USE_ATL)
     # Required by VS2013 to link accessibility API functions.
diff --git tests/cefclient/browser/browser_window_osr_gtk.cc tests/cefclient/browser/browser_window_osr_gtk.cc
index 0d4a7846..44701f3e 100644
--- tests/cefclient/browser/browser_window_osr_gtk.cc
+++ tests/cefclient/browser/browser_window_osr_gtk.cc
@@ -928,7 +928,7 @@ class ScopedGLContext {
 
 BrowserWindowOsrGtk::BrowserWindowOsrGtk(BrowserWindow::Delegate* delegate,
                                          const std::string& startup_url,
-                                         const OsrRenderer::Settings& settings)
+                                         const OsrRendererSettings& settings)
     : BrowserWindow(delegate),
       renderer_(settings),
       glarea_(NULL),
diff --git tests/cefclient/browser/browser_window_osr_gtk.h tests/cefclient/browser/browser_window_osr_gtk.h
index e52feb61..a7fb76d0 100644
--- tests/cefclient/browser/browser_window_osr_gtk.h
+++ tests/cefclient/browser/browser_window_osr_gtk.h
@@ -22,7 +22,7 @@ class BrowserWindowOsrGtk : public BrowserWindow,
   // |delegate| must outlive this object.
   BrowserWindowOsrGtk(BrowserWindow::Delegate* delegate,
                       const std::string& startup_url,
-                      const OsrRenderer::Settings& settings);
+                      const OsrRendererSettings& settings);
 
   // BrowserWindow methods.
   void CreateBrowser(ClientWindowHandle parent_handle,
diff --git tests/cefclient/browser/browser_window_osr_mac.h tests/cefclient/browser/browser_window_osr_mac.h
index 7dcbd333..5300e990 100644
--- tests/cefclient/browser/browser_window_osr_mac.h
+++ tests/cefclient/browser/browser_window_osr_mac.h
@@ -23,7 +23,7 @@ class BrowserWindowOsrMac : public BrowserWindow,
   // |delegate| must outlive this object.
   BrowserWindowOsrMac(BrowserWindow::Delegate* delegate,
                       const std::string& startup_url,
-                      const OsrRenderer::Settings& settings);
+                      const OsrRendererSettings& settings);
   ~BrowserWindowOsrMac();
 
   // BrowserWindow methods.
diff --git tests/cefclient/browser/browser_window_osr_mac.mm tests/cefclient/browser/browser_window_osr_mac.mm
index 66a036cf..ef8c2a71 100644
--- tests/cefclient/browser/browser_window_osr_mac.mm
+++ tests/cefclient/browser/browser_window_osr_mac.mm
@@ -1208,7 +1208,7 @@ namespace client {
 
 BrowserWindowOsrMac::BrowserWindowOsrMac(BrowserWindow::Delegate* delegate,
                                          const std::string& startup_url,
-                                         const OsrRenderer::Settings& settings)
+                                         const OsrRendererSettings& settings)
     : BrowserWindow(delegate),
       renderer_(settings),
       nsview_(NULL),
diff --git tests/cefclient/browser/browser_window_osr_win.cc tests/cefclient/browser/browser_window_osr_win.cc
index 2b1851fb..44573291 100644
--- tests/cefclient/browser/browser_window_osr_win.cc
+++ tests/cefclient/browser/browser_window_osr_win.cc
@@ -10,7 +10,7 @@ namespace client {
 
 BrowserWindowOsrWin::BrowserWindowOsrWin(BrowserWindow::Delegate* delegate,
                                          const std::string& startup_url,
-                                         const OsrRenderer::Settings& settings)
+                                         const OsrRendererSettings& settings)
     : BrowserWindow(delegate), osr_hwnd_(NULL), device_scale_factor_(0) {
   osr_window_ = new OsrWindowWin(this, settings);
   client_handler_ = new ClientHandlerOsr(this, osr_window_.get(), startup_url);
@@ -36,6 +36,11 @@ void BrowserWindowOsrWin::GetPopupConfig(CefWindowHandle temp_handle,
   CEF_REQUIRE_UI_THREAD();
 
   windowInfo.SetAsWindowless(temp_handle);
+  windowInfo.shared_texture_enabled =
+      osr_window_->settings().shared_texture_enabled;
+  windowInfo.external_begin_frame_enabled =
+      osr_window_->settings().external_begin_frame_enabled;
+
   client = client_handler_;
 }
 
diff --git tests/cefclient/browser/browser_window_osr_win.h tests/cefclient/browser/browser_window_osr_win.h
index 9064796f..3fce9e2f 100644
--- tests/cefclient/browser/browser_window_osr_win.h
+++ tests/cefclient/browser/browser_window_osr_win.h
@@ -21,7 +21,7 @@ class BrowserWindowOsrWin : public BrowserWindow,
   // |delegate| must outlive this object.
   BrowserWindowOsrWin(BrowserWindow::Delegate* delegate,
                       const std::string& startup_url,
-                      const OsrRenderer::Settings& settings);
+                      const OsrRendererSettings& settings);
 
   // BrowserWindow methods.
   void CreateBrowser(ClientWindowHandle parent_handle,
diff --git tests/cefclient/browser/client_handler.cc tests/cefclient/browser/client_handler.cc
index c68d9339..39e40882 100644
--- tests/cefclient/browser/client_handler.cc
+++ tests/cefclient/browser/client_handler.cc
@@ -831,6 +831,8 @@ void ClientHandler::ShowDevTools(CefRefPtr<CefBrowser> browser,
   CefRefPtr<CefClient> client;
   CefBrowserSettings settings;
 
+  MainContext::Get()->PopulateBrowserSettings(&settings);
+
   CefRefPtr<CefBrowserHost> host = browser->GetHost();
 
   // Test if the DevTools browser already exists.
diff --git tests/cefclient/browser/client_handler_osr.cc tests/cefclient/browser/client_handler_osr.cc
index a2a2863f..5b147f99 100644
--- tests/cefclient/browser/client_handler_osr.cc
+++ tests/cefclient/browser/client_handler_osr.cc
@@ -104,6 +104,17 @@ void ClientHandlerOsr::OnPaint(CefRefPtr<CefBrowser> browser,
   osr_delegate_->OnPaint(browser, type, dirtyRects, buffer, width, height);
 }
 
+void ClientHandlerOsr::OnAcceleratedPaint(
+    CefRefPtr<CefBrowser> browser,
+    CefRenderHandler::PaintElementType type,
+    const CefRenderHandler::RectList& dirtyRects,
+    void* share_handle) {
+  CEF_REQUIRE_UI_THREAD();
+  if (!osr_delegate_)
+    return;
+  osr_delegate_->OnAcceleratedPaint(browser, type, dirtyRects, share_handle);
+}
+
 void ClientHandlerOsr::OnCursorChange(CefRefPtr<CefBrowser> browser,
                                       CefCursorHandle cursor,
                                       CursorType type,
diff --git tests/cefclient/browser/client_handler_osr.h tests/cefclient/browser/client_handler_osr.h
index 42723102..35627aba 100644
--- tests/cefclient/browser/client_handler_osr.h
+++ tests/cefclient/browser/client_handler_osr.h
@@ -39,11 +39,16 @@ class ClientHandlerOsr : public ClientHandler,
     virtual void OnPopupSize(CefRefPtr<CefBrowser> browser,
                              const CefRect& rect) = 0;
     virtual void OnPaint(CefRefPtr<CefBrowser> browser,
-                         PaintElementType type,
-                         const RectList& dirtyRects,
+                         CefRenderHandler::PaintElementType type,
+                         const CefRenderHandler::RectList& dirtyRects,
                          const void* buffer,
                          int width,
                          int height) = 0;
+    virtual void OnAcceleratedPaint(
+        CefRefPtr<CefBrowser> browser,
+        CefRenderHandler::PaintElementType type,
+        const CefRenderHandler::RectList& dirtyRects,
+        void* share_handle) {}
     virtual void OnCursorChange(CefRefPtr<CefBrowser> browser,
                                 CefCursorHandle cursor,
                                 CefRenderHandler::CursorType type,
@@ -103,6 +108,10 @@ class ClientHandlerOsr : public ClientHandler,
                const void* buffer,
                int width,
                int height) OVERRIDE;
+  void OnAcceleratedPaint(CefRefPtr<CefBrowser> browser,
+                          CefRenderHandler::PaintElementType type,
+                          const CefRenderHandler::RectList& dirtyRects,
+                          void* share_handle) OVERRIDE;
   void OnCursorChange(CefRefPtr<CefBrowser> browser,
                       CefCursorHandle cursor,
                       CursorType type,
diff --git tests/cefclient/browser/main_context.h tests/cefclient/browser/main_context.h
index b1db6b8f..c7b04cd9 100644
--- tests/cefclient/browser/main_context.h
+++ tests/cefclient/browser/main_context.h
@@ -10,10 +10,16 @@
 
 #include "include/base/cef_ref_counted.h"
 #include "include/internal/cef_types_wrappers.h"
-#include "tests/cefclient/browser/osr_renderer.h"
+#include "tests/cefclient/browser/osr_renderer_settings.h"
 
 namespace client {
 
+#if defined(OS_WIN)
+namespace d3d11 {
+class Device;
+}
+#endif
+
 class RootWindowManager;
 
 // Used to store global context in the browser process. The methods of this
@@ -47,11 +53,15 @@ class MainContext {
   // Populate |settings| based on command-line arguments.
   virtual void PopulateSettings(CefSettings* settings) = 0;
   virtual void PopulateBrowserSettings(CefBrowserSettings* settings) = 0;
-  virtual void PopulateOsrSettings(OsrRenderer::Settings* settings) = 0;
+  virtual void PopulateOsrSettings(OsrRendererSettings* settings) = 0;
 
   // Returns the object used to create/manage RootWindow instances.
   virtual RootWindowManager* GetRootWindowManager() = 0;
 
+#if defined(OS_WIN)
+  virtual std::shared_ptr<d3d11::Device> GetD3D11Device() = 0;
+#endif
+
  protected:
   MainContext();
   virtual ~MainContext();
diff --git tests/cefclient/browser/main_context_impl.cc tests/cefclient/browser/main_context_impl.cc
index 5ccedae7..ba6fb625 100644
--- tests/cefclient/browser/main_context_impl.cc
+++ tests/cefclient/browser/main_context_impl.cc
@@ -46,6 +46,7 @@ MainContextImpl::MainContextImpl(CefRefPtr<CefCommandLine> command_line,
       shutdown_(false),
       background_color_(0),
       browser_background_color_(0),
+      windowless_frame_rate_(0),
       use_views_(false) {
   DCHECK(command_line_.get());
 
@@ -59,11 +60,35 @@ MainContextImpl::MainContextImpl(CefRefPtr<CefCommandLine> command_line,
   use_windowless_rendering_ =
       command_line_->HasSwitch(switches::kOffScreenRenderingEnabled);
 
+  if (use_windowless_rendering_ &&
+      command_line_->HasSwitch(switches::kOffScreenFrameRate)) {
+    windowless_frame_rate_ =
+        atoi(command_line_->GetSwitchValue(switches::kOffScreenFrameRate)
+                 .ToString()
+                 .c_str());
+  }
+
   // Whether transparent painting is used with windowless rendering.
   const bool use_transparent_painting =
       use_windowless_rendering_ &&
       command_line_->HasSwitch(switches::kTransparentPaintingEnabled);
 
+#if defined(OS_WIN)
+  // Shared texture is only supported on Windows.
+  shared_texture_enabled_ =
+      use_windowless_rendering_ &&
+      command_line_->HasSwitch(switches::kSharedTextureEnabled);
+#endif
+
+  external_begin_frame_enabled_ =
+      use_windowless_rendering_ &&
+      command_line_->HasSwitch(switches::kExternalBeginFrameEnabled);
+
+  if (windowless_frame_rate_ <= 0) {
+    // Choose a reasonable default rate based on the OSR mode.
+    windowless_frame_rate_ = shared_texture_enabled_ ? 60 : 30;
+  }
+
 #if defined(OS_WIN) || defined(OS_LINUX)
   // Whether the Views framework will be used.
   use_views_ = command_line_->HasSwitch(switches::kUseViews);
@@ -156,21 +181,22 @@ void MainContextImpl::PopulateSettings(CefSettings* settings) {
 }
 
 void MainContextImpl::PopulateBrowserSettings(CefBrowserSettings* settings) {
-  if (command_line_->HasSwitch(switches::kOffScreenFrameRate)) {
-    settings->windowless_frame_rate =
-        atoi(command_line_->GetSwitchValue(switches::kOffScreenFrameRate)
-                 .ToString()
-                 .c_str());
-  }
+  settings->windowless_frame_rate = windowless_frame_rate_;
 
   if (browser_background_color_ != 0)
     settings->background_color = browser_background_color_;
 }
 
-void MainContextImpl::PopulateOsrSettings(OsrRenderer::Settings* settings) {
+void MainContextImpl::PopulateOsrSettings(OsrRendererSettings* settings) {
   settings->show_update_rect =
       command_line_->HasSwitch(switches::kShowUpdateRect);
 
+#if defined(OS_WIN)
+  settings->shared_texture_enabled = shared_texture_enabled_;
+#endif
+  settings->external_begin_frame_enabled = external_begin_frame_enabled_;
+  settings->begin_frame_rate = windowless_frame_rate_;
+
   if (browser_background_color_ != 0)
     settings->background_color = browser_background_color_;
 }
diff --git tests/cefclient/browser/main_context_impl.h tests/cefclient/browser/main_context_impl.h
index a58784ca..c7a1d837 100644
--- tests/cefclient/browser/main_context_impl.h
+++ tests/cefclient/browser/main_context_impl.h
@@ -31,9 +31,13 @@ class MainContextImpl : public MainContext {
   bool UseWindowlessRendering() OVERRIDE;
   void PopulateSettings(CefSettings* settings) OVERRIDE;
   void PopulateBrowserSettings(CefBrowserSettings* settings) OVERRIDE;
-  void PopulateOsrSettings(OsrRenderer::Settings* settings) OVERRIDE;
+  void PopulateOsrSettings(OsrRendererSettings* settings) OVERRIDE;
   RootWindowManager* GetRootWindowManager() OVERRIDE;
 
+#if defined(OS_WIN)
+  std::shared_ptr<d3d11::Device> GetD3D11Device() OVERRIDE;
+#endif
+
   // Initialize CEF and associated main context state. This method must be
   // called on the same thread that created this object.
   bool Initialize(const CefMainArgs& args,
@@ -68,10 +72,17 @@ class MainContextImpl : public MainContext {
   cef_color_t background_color_;
   cef_color_t browser_background_color_;
   bool use_windowless_rendering_;
+  int windowless_frame_rate_;
   bool use_views_;
 
   scoped_ptr<RootWindowManager> root_window_manager_;
 
+#if defined(OS_WIN)
+  bool shared_texture_enabled_;
+  bool external_begin_frame_enabled_;
+  std::shared_ptr<d3d11::Device> d3d11_device_;
+#endif
+
   // Used to verify that methods are called on the correct thread.
   base::ThreadChecker thread_checker_;
 
diff --git tests/cefclient/browser/main_context_impl_win.cc tests/cefclient/browser/main_context_impl_win.cc
index 4bf4d588..d52c0943 100644
--- tests/cefclient/browser/main_context_impl_win.cc
+++ tests/cefclient/browser/main_context_impl_win.cc
@@ -7,6 +7,8 @@
 #include <direct.h>
 #include <shlobj.h>
 
+#include "tests/cefclient/browser/osr_d3d11_win.h"
+
 namespace client {
 
 std::string MainContextImpl::GetDownloadPath(const std::string& file_name) {
@@ -36,4 +38,11 @@ std::string MainContextImpl::GetAppWorkingDirectory() {
   return szWorkingDir;
 }
 
+std::shared_ptr<d3d11::Device> MainContextImpl::GetD3D11Device() {
+  CEF_REQUIRE_UI_THREAD();
+  if (!d3d11_device_)
+    d3d11_device_ = d3d11::Device::create();
+  return d3d11_device_;
+}
+
 }  // namespace client
diff --git tests/cefclient/browser/osr_d3d11_win.cc tests/cefclient/browser/osr_d3d11_win.cc
new file mode 100644
index 00000000..5e336c73
--- /dev/null
+++ tests/cefclient/browser/osr_d3d11_win.cc
@@ -0,0 +1,931 @@
+// Copyright 2018 The Chromium Embedded Framework Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be found
+// in the LICENSE file.
+//
+// Portions Copyright (c) 2018 Daktronics with the following MIT License:
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+
+#include "tests/cefclient/browser/osr_d3d11_win.h"
+
+#include <iomanip>  // For std::setw.
+
+#include <d3dcompiler.h>
+#include <directxmath.h>
+
+#include "include/base/cef_logging.h"
+#include "include/internal/cef_string.h"
+#include "tests/shared/browser/util_win.h"
+
+namespace client {
+namespace d3d11 {
+
+namespace {
+
+// Wrap a raw COM pointer in a shared_ptr for auto Release().
+template <class T>
+std::shared_ptr<T> to_com_ptr(T* obj) {
+  return std::shared_ptr<T>(obj, [](T* p) {
+    if (p)
+      p->Release();
+  });
+}
+
+}  // namespace
+
+struct SimpleVertex {
+  DirectX::XMFLOAT3 pos;
+  DirectX::XMFLOAT2 tex;
+};
+
+Context::Context(ID3D11DeviceContext* ctx) : ctx_(to_com_ptr(ctx)) {}
+
+void Context::flush() {
+  ctx_->Flush();
+}
+
+SwapChain::SwapChain(IDXGISwapChain* swapchain,
+                     ID3D11RenderTargetView* rtv,
+                     ID3D11SamplerState* sampler,
+                     ID3D11BlendState* blender)
+    : sampler_(to_com_ptr(sampler)),
+      blender_(to_com_ptr(blender)),
+      swapchain_(to_com_ptr(swapchain)),
+      rtv_(to_com_ptr(rtv)),
+      width_(0),
+      height_(0) {}
+
+void SwapChain::bind(const std::shared_ptr<Context>& ctx) {
+  ctx_ = ctx;
+  ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
+
+  ID3D11RenderTargetView* rtv[1] = {rtv_.get()};
+  d3d11_ctx->OMSetRenderTargets(1, rtv, nullptr);
+
+  // Set default blending state.
+  if (blender_) {
+    float factor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+    d3d11_ctx->OMSetBlendState(blender_.get(), factor, 0xffffffff);
+  }
+
+  // Set default sampler state.
+  if (sampler_) {
+    ID3D11SamplerState* samplers[1] = {sampler_.get()};
+    d3d11_ctx->PSSetSamplers(0, 1, samplers);
+  }
+}
+
+void SwapChain::unbind() {
+  ctx_.reset();
+}
+
+void SwapChain::clear(float red, float green, float blue, float alpha) {
+  ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
+  CHECK(d3d11_ctx);
+
+  FLOAT color[4] = {red, green, blue, alpha};
+  d3d11_ctx->ClearRenderTargetView(rtv_.get(), color);
+}
+
+void SwapChain::present(int sync_interval) {
+  swapchain_->Present(sync_interval, 0);
+}
+
+void SwapChain::resize(int width, int height) {
+  if (width <= 0 || height <= 0 || width == width_ || height == height_) {
+    return;
+  }
+  width_ = width;
+  height_ = height;
+
+  ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
+  CHECK(d3d11_ctx);
+
+  d3d11_ctx->OMSetRenderTargets(0, 0, 0);
+  rtv_.reset();
+
+  DXGI_SWAP_CHAIN_DESC desc;
+  swapchain_->GetDesc(&desc);
+  auto hr = swapchain_->ResizeBuffers(0, width, height, desc.BufferDesc.Format,
+                                      desc.Flags);
+  if (FAILED(hr)) {
+    LOG(ERROR) << "d3d11: Failed to resize swapchain (" << width << "x"
+               << height << ")";
+    return;
+  }
+
+  ID3D11Texture2D* buffer = nullptr;
+  hr = swapchain_->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&buffer);
+  if (FAILED(hr)) {
+    LOG(ERROR) << "d3d11: Failed to resize swapchain (" << width << "x"
+               << height << ")";
+    return;
+  }
+
+  ID3D11Device* dev = nullptr;
+  d3d11_ctx->GetDevice(&dev);
+  if (dev) {
+    D3D11_RENDER_TARGET_VIEW_DESC vdesc = {};
+    vdesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
+    vdesc.Texture2D.MipSlice = 0;
+    vdesc.Format = desc.BufferDesc.Format;
+
+    ID3D11RenderTargetView* view = nullptr;
+    hr = dev->CreateRenderTargetView(buffer, &vdesc, &view);
+    if (SUCCEEDED(hr)) {
+      rtv_ = to_com_ptr(view);
+      d3d11_ctx->OMSetRenderTargets(1, &view, nullptr);
+    }
+    dev->Release();
+  }
+  buffer->Release();
+
+  D3D11_VIEWPORT vp;
+  vp.Width = static_cast<float>(width);
+  vp.Height = static_cast<float>(height);
+  vp.MinDepth = D3D11_MIN_DEPTH;
+  vp.MaxDepth = D3D11_MAX_DEPTH;
+  vp.TopLeftX = 0;
+  vp.TopLeftY = 0;
+  d3d11_ctx->RSSetViewports(1, &vp);
+}
+
+Effect::Effect(ID3D11VertexShader* vsh,
+               ID3D11PixelShader* psh,
+               ID3D11InputLayout* layout)
+    : vsh_(to_com_ptr(vsh)),
+      psh_(to_com_ptr(psh)),
+      layout_(to_com_ptr(layout)) {}
+
+void Effect::bind(const std::shared_ptr<Context>& ctx) {
+  ctx_ = ctx;
+  ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
+
+  d3d11_ctx->IASetInputLayout(layout_.get());
+  d3d11_ctx->VSSetShader(vsh_.get(), nullptr, 0);
+  d3d11_ctx->PSSetShader(psh_.get(), nullptr, 0);
+}
+
+void Effect::unbind() {}
+
+Geometry::Geometry(D3D_PRIMITIVE_TOPOLOGY primitive,
+                   uint32_t vertices,
+                   uint32_t stride,
+                   ID3D11Buffer* buffer)
+    : primitive_(primitive),
+      vertices_(vertices),
+      stride_(stride),
+      buffer_(to_com_ptr(buffer)) {}
+
+void Geometry::bind(const std::shared_ptr<Context>& ctx) {
+  ctx_ = ctx;
+  ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
+
+  // TODO: Handle offset.
+  uint32_t offset = 0;
+
+  ID3D11Buffer* buffers[1] = {buffer_.get()};
+  d3d11_ctx->IASetVertexBuffers(0, 1, buffers, &stride_, &offset);
+  d3d11_ctx->IASetPrimitiveTopology(primitive_);
+}
+
+void Geometry::unbind() {}
+
+void Geometry::draw() {
+  ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
+  CHECK(d3d11_ctx);
+
+  // TODO: Handle offset.
+  d3d11_ctx->Draw(vertices_, 0);
+}
+
+Texture2D::Texture2D(ID3D11Texture2D* tex, ID3D11ShaderResourceView* srv)
+    : texture_(to_com_ptr(tex)), srv_(to_com_ptr(srv)) {
+  share_handle_ = nullptr;
+
+  IDXGIResource* res = nullptr;
+  if (SUCCEEDED(texture_->QueryInterface(__uuidof(IDXGIResource),
+                                         reinterpret_cast<void**>(&res)))) {
+    res->GetSharedHandle(&share_handle_);
+    res->Release();
+  }
+
+  // Are we using a keyed mutex?
+  IDXGIKeyedMutex* mutex = nullptr;
+  if (SUCCEEDED(texture_->QueryInterface(__uuidof(IDXGIKeyedMutex),
+                                         (void**)&mutex))) {
+    keyed_mutex_ = to_com_ptr(mutex);
+  }
+}
+
+uint32_t Texture2D::width() const {
+  D3D11_TEXTURE2D_DESC desc;
+  texture_->GetDesc(&desc);
+  return desc.Width;
+}
+
+uint32_t Texture2D::height() const {
+  D3D11_TEXTURE2D_DESC desc;
+  texture_->GetDesc(&desc);
+  return desc.Height;
+}
+
+DXGI_FORMAT Texture2D::format() const {
+  D3D11_TEXTURE2D_DESC desc;
+  texture_->GetDesc(&desc);
+  return desc.Format;
+}
+
+bool Texture2D::has_mutex() const {
+  return (keyed_mutex_.get() != nullptr);
+}
+
+bool Texture2D::lock_key(uint64_t key, uint32_t timeout_ms) {
+  if (keyed_mutex_) {
+    const auto hr = keyed_mutex_->AcquireSync(key, timeout_ms);
+    return SUCCEEDED(hr);
+  }
+  return true;
+}
+
+void Texture2D::unlock_key(uint64_t key) {
+  if (keyed_mutex_) {
+    keyed_mutex_->ReleaseSync(key);
+  }
+}
+
+void Texture2D::bind(const std::shared_ptr<Context>& ctx) {
+  ctx_ = ctx;
+  ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
+  if (srv_) {
+    ID3D11ShaderResourceView* views[1] = {srv_.get()};
+    d3d11_ctx->PSSetShaderResources(0, 1, views);
+  }
+}
+
+void Texture2D::unbind() {}
+
+void* Texture2D::share_handle() const {
+  return share_handle_;
+}
+
+void Texture2D::copy_from(const std::shared_ptr<Texture2D>& other) {
+  ID3D11DeviceContext* d3d11_ctx = (ID3D11DeviceContext*)(*ctx_);
+  CHECK(d3d11_ctx);
+  if (other) {
+    d3d11_ctx->CopyResource(texture_.get(), other->texture_.get());
+  }
+}
+
+Device::Device(ID3D11Device* pdev, ID3D11DeviceContext* pctx)
+    : device_(to_com_ptr(pdev)), ctx_(std::make_shared<Context>(pctx)) {
+  lib_compiler_ = LoadLibrary(L"d3dcompiler_47.dll");
+}
+
+// static
+std::shared_ptr<Device> Device::create() {
+  UINT flags = 0;
+#ifdef _DEBUG
+  flags |= D3D11_CREATE_DEVICE_DEBUG;
+#endif
+
+  D3D_FEATURE_LEVEL feature_levels[] = {
+      D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
+      D3D_FEATURE_LEVEL_10_0,
+      // D3D_FEATURE_LEVEL_9_3,
+  };
+  UINT num_feature_levels = sizeof(feature_levels) / sizeof(feature_levels[0]);
+
+  ID3D11Device* pdev = nullptr;
+  ID3D11DeviceContext* pctx = nullptr;
+
+  D3D_FEATURE_LEVEL selected_level;
+  HRESULT hr = D3D11CreateDevice(
+      nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags, feature_levels,
+      num_feature_levels, D3D11_SDK_VERSION, &pdev, &selected_level, &pctx);
+
+  if (hr == E_INVALIDARG) {
+    // DirectX 11.0 platforms will not recognize D3D_FEATURE_LEVEL_11_1
+    // so we need to retry without it.
+    hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags,
+                           &feature_levels[1], num_feature_levels - 1,
+                           D3D11_SDK_VERSION, &pdev, &selected_level, &pctx);
+  }
+
+  if (SUCCEEDED(hr)) {
+    const auto dev = std::make_shared<Device>(pdev, pctx);
+
+    LOG(INFO) << "d3d11: Selected adapter " << dev->adapter_name()
+              << " and feature level 0x" << std::setw(4) << std::hex
+              << selected_level;
+
+    return dev;
+  }
+
+  return nullptr;
+}
+
+std::string Device::adapter_name() const {
+  IDXGIDevice* dxgi_dev = nullptr;
+  auto hr = device_->QueryInterface(__uuidof(dxgi_dev), (void**)&dxgi_dev);
+  if (SUCCEEDED(hr)) {
+    IDXGIAdapter* dxgi_adapt = nullptr;
+    hr = dxgi_dev->GetAdapter(&dxgi_adapt);
+    dxgi_dev->Release();
+    if (SUCCEEDED(hr)) {
+      DXGI_ADAPTER_DESC desc;
+      hr = dxgi_adapt->GetDesc(&desc);
+      dxgi_adapt->Release();
+      if (SUCCEEDED(hr)) {
+        return CefString(desc.Description);
+      }
+    }
+  }
+
+  return "n/a";
+}
+
+std::shared_ptr<Context> Device::immedidate_context() {
+  return ctx_;
+}
+
+std::shared_ptr<SwapChain> Device::create_swapchain(HWND window,
+                                                    int width,
+                                                    int height) {
+  HRESULT hr;
+  IDXGIFactory1* dxgi_factory = nullptr;
+
+  // Default size to the window size unless specified.
+  RECT rc_bounds;
+  GetClientRect(window, &rc_bounds);
+  if (width <= 0) {
+    width = rc_bounds.right - rc_bounds.left;
+  }
+  if (height <= 0) {
+    height = rc_bounds.bottom - rc_bounds.top;
+  }
+
+  {
+    IDXGIDevice* dxgi_dev = nullptr;
+    hr = device_->QueryInterface(__uuidof(IDXGIDevice),
+                                 reinterpret_cast<void**>(&dxgi_dev));
+    if (FAILED(hr)) {
+      return nullptr;
+    }
+
+    IDXGIAdapter* adapter = nullptr;
+    hr = dxgi_dev->GetAdapter(&adapter);
+    dxgi_dev->Release();
+    if (FAILED(hr)) {
+      return nullptr;
+    }
+
+    hr = adapter->GetParent(__uuidof(IDXGIFactory1),
+                            reinterpret_cast<void**>(&dxgi_factory));
+    adapter->Release();
+  }
+
+  if (!dxgi_factory) {
+    return nullptr;
+  }
+
+  IDXGISwapChain* swapchain = nullptr;
+
+  // Create swap chain.
+  IDXGIFactory2* dxgi_factory2 = nullptr;
+  hr = dxgi_factory->QueryInterface(__uuidof(IDXGIFactory2),
+                                    reinterpret_cast<void**>(&dxgi_factory2));
+  if (dxgi_factory2) {
+    DXGI_SWAP_CHAIN_DESC1 sd;
+    ZeroMemory(&sd, sizeof(sd));
+    sd.Width = width;
+    sd.Height = height;
+    sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+    sd.SampleDesc.Count = 1;
+    sd.SampleDesc.Quality = 0;
+    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+    sd.BufferCount = 1;
+
+    IDXGISwapChain1* swapchain1 = nullptr;
+    hr = dxgi_factory2->CreateSwapChainForHwnd(device_.get(), window, &sd,
+                                               nullptr, nullptr, &swapchain1);
+    if (SUCCEEDED(hr)) {
+      hr = swapchain1->QueryInterface(__uuidof(IDXGISwapChain),
+                                      reinterpret_cast<void**>(&swapchain));
+      swapchain1->Release();
+    }
+
+    dxgi_factory2->Release();
+  } else {
+    // DirectX 11.0 systems.
+    DXGI_SWAP_CHAIN_DESC sd;
+    ZeroMemory(&sd, sizeof(sd));
+    sd.BufferCount = 1;
+    sd.BufferDesc.Width = width;
+    sd.BufferDesc.Height = height;
+    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
+    sd.BufferDesc.RefreshRate.Numerator = 60;
+    sd.BufferDesc.RefreshRate.Denominator = 1;
+    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+    sd.OutputWindow = window;
+    sd.SampleDesc.Count = 1;
+    sd.SampleDesc.Quality = 0;
+    sd.Windowed = TRUE;
+
+    hr = dxgi_factory->CreateSwapChain(device_.get(), &sd, &swapchain);
+  }
+
+  // We don't handle full-screen swapchains so we block the ALT+ENTER shortcut.
+  dxgi_factory->MakeWindowAssociation(window, DXGI_MWA_NO_ALT_ENTER);
+  dxgi_factory->Release();
+
+  if (!swapchain) {
+    return nullptr;
+  }
+
+  ID3D11Texture2D* back_buffer = nullptr;
+  hr = swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D),
+                            reinterpret_cast<void**>(&back_buffer));
+  if (FAILED(hr)) {
+    swapchain->Release();
+    return nullptr;
+  }
+
+  ID3D11RenderTargetView* rtv = nullptr;
+  hr = device_->CreateRenderTargetView(back_buffer, nullptr, &rtv);
+  back_buffer->Release();
+  if (FAILED(hr)) {
+    swapchain->Release();
+    return nullptr;
+  }
+
+  const auto ctx = (ID3D11DeviceContext*)(*ctx_);
+
+  ctx->OMSetRenderTargets(1, &rtv, nullptr);
+
+  // Setup the viewport.
+  D3D11_VIEWPORT vp;
+  vp.Width = (FLOAT)width;
+  vp.Height = (FLOAT)height;
+  vp.MinDepth = 0.0f;
+  vp.MaxDepth = 1.0f;
+  vp.TopLeftX = 0;
+  vp.TopLeftY = 0;
+  ctx->RSSetViewports(1, &vp);
+
+  // Create a default sampler to use.
+  ID3D11SamplerState* sampler = nullptr;
+  {
+    D3D11_SAMPLER_DESC desc = {};
+    desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
+    desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
+    desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
+    desc.ComparisonFunc = D3D11_COMPARISON_NEVER;
+    desc.MinLOD = 0.0f;
+    desc.MaxLOD = D3D11_FLOAT32_MAX;
+    desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
+    device_->CreateSamplerState(&desc, &sampler);
+  }
+
+  // Create a default blend state to use (pre-multiplied alpha).
+  ID3D11BlendState* blender = nullptr;
+  {
+    D3D11_BLEND_DESC desc;
+    desc.AlphaToCoverageEnable = FALSE;
+    desc.IndependentBlendEnable = FALSE;
+    const auto count = sizeof(desc.RenderTarget) / sizeof(desc.RenderTarget[0]);
+    for (size_t n = 0; n < count; ++n) {
+      desc.RenderTarget[n].BlendEnable = TRUE;
+      desc.RenderTarget[n].SrcBlend = D3D11_BLEND_ONE;
+      desc.RenderTarget[n].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
+      desc.RenderTarget[n].SrcBlendAlpha = D3D11_BLEND_ONE;
+      desc.RenderTarget[n].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
+      desc.RenderTarget[n].BlendOp = D3D11_BLEND_OP_ADD;
+      desc.RenderTarget[n].BlendOpAlpha = D3D11_BLEND_OP_ADD;
+      desc.RenderTarget[n].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;
+    }
+    device_->CreateBlendState(&desc, &blender);
+  }
+
+  return std::make_shared<SwapChain>(swapchain, rtv, sampler, blender);
+}
+
+std::shared_ptr<Geometry> Device::create_quad(float x,
+                                              float y,
+                                              float width,
+                                              float height,
+                                              bool flip) {
+  x = (x * 2.0f) - 1.0f;
+  y = 1.0f - (y * 2.0f);
+  width = width * 2.0f;
+  height = height * 2.0f;
+  float z = 1.0f;
+
+  SimpleVertex vertices[] = {
+
+      {DirectX::XMFLOAT3(x, y, z), DirectX::XMFLOAT2(0.0f, 0.0f)},
+      {DirectX::XMFLOAT3(x + width, y, z), DirectX::XMFLOAT2(1.0f, 0.0f)},
+      {DirectX::XMFLOAT3(x, y - height, z), DirectX::XMFLOAT2(0.0f, 1.0f)},
+      {DirectX::XMFLOAT3(x + width, y - height, z),
+       DirectX::XMFLOAT2(1.0f, 1.0f)}};
+
+  if (flip) {
+    DirectX::XMFLOAT2 tmp(vertices[2].tex);
+    vertices[2].tex = vertices[0].tex;
+    vertices[0].tex = tmp;
+
+    tmp = vertices[3].tex;
+    vertices[3].tex = vertices[1].tex;
+    vertices[1].tex = tmp;
+  }
+
+  D3D11_BUFFER_DESC desc = {};
+  desc.Usage = D3D11_USAGE_DEFAULT;
+  desc.ByteWidth = sizeof(SimpleVertex) * 4;
+  desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
+  desc.CPUAccessFlags = 0;
+
+  D3D11_SUBRESOURCE_DATA srd = {};
+  srd.pSysMem = vertices;
+
+  ID3D11Buffer* buffer = nullptr;
+  const auto hr = device_->CreateBuffer(&desc, &srd, &buffer);
+  if (SUCCEEDED(hr)) {
+    return std::make_shared<Geometry>(
+        D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, 4,
+        static_cast<uint32_t>(sizeof(SimpleVertex)), buffer);
+  }
+
+  return nullptr;
+}
+
+std::shared_ptr<Texture2D> Device::open_shared_texture(void* handle) {
+  ID3D11Texture2D* tex = nullptr;
+  auto hr = device_->OpenSharedResource(handle, __uuidof(ID3D11Texture2D),
+                                        (void**)(&tex));
+  if (FAILED(hr)) {
+    return nullptr;
+  }
+
+  D3D11_TEXTURE2D_DESC td;
+  tex->GetDesc(&td);
+
+  ID3D11ShaderResourceView* srv = nullptr;
+
+  if (td.BindFlags & D3D11_BIND_SHADER_RESOURCE) {
+    D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc;
+    srv_desc.Format = td.Format;
+    srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
+    srv_desc.Texture2D.MostDetailedMip = 0;
+    srv_desc.Texture2D.MipLevels = 1;
+
+    hr = device_->CreateShaderResourceView(tex, &srv_desc, &srv);
+    if (FAILED(hr)) {
+      tex->Release();
+      return nullptr;
+    }
+  }
+
+  return std::make_shared<Texture2D>(tex, srv);
+}
+
+std::shared_ptr<Texture2D> Device::create_texture(int width,
+                                                  int height,
+                                                  DXGI_FORMAT format,
+                                                  const void* data,
+                                                  size_t row_stride) {
+  D3D11_TEXTURE2D_DESC td;
+  td.ArraySize = 1;
+  td.BindFlags = D3D11_BIND_SHADER_RESOURCE;
+  td.CPUAccessFlags = 0;
+  td.Format = format;
+  td.Width = width;
+  td.Height = height;
+  td.MipLevels = 1;
+  td.MiscFlags = 0;
+  td.SampleDesc.Count = 1;
+  td.SampleDesc.Quality = 0;
+  td.Usage = D3D11_USAGE_DEFAULT;
+
+  D3D11_SUBRESOURCE_DATA srd;
+  srd.pSysMem = data;
+  srd.SysMemPitch = static_cast<uint32_t>(row_stride);
+  srd.SysMemSlicePitch = 0;
+
+  ID3D11Texture2D* tex = nullptr;
+  auto hr = device_->CreateTexture2D(&td, data ? &srd : nullptr, &tex);
+  if (FAILED(hr)) {
+    return nullptr;
+  }
+
+  D3D11_SHADER_RESOURCE_VIEW_DESC srv_desc;
+  srv_desc.Format = td.Format;
+  srv_desc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
+  srv_desc.Texture2D.MostDetailedMip = 0;
+  srv_desc.Texture2D.MipLevels = 1;
+
+  ID3D11ShaderResourceView* srv = nullptr;
+  hr = device_->CreateShaderResourceView(tex, &srv_desc, &srv);
+  if (FAILED(hr)) {
+    tex->Release();
+    return nullptr;
+  }
+
+  return std::make_shared<Texture2D>(tex, srv);
+}
+
+std::shared_ptr<ID3DBlob> Device::compile_shader(const std::string& source_code,
+                                                 const std::string& entry_point,
+                                                 const std::string& model) {
+  if (!lib_compiler_) {
+    return nullptr;
+  }
+
+  typedef HRESULT(WINAPI * PFN_D3DCOMPILE)(
+      LPCVOID, SIZE_T, LPCSTR, const D3D_SHADER_MACRO*, ID3DInclude*, LPCSTR,
+      LPCSTR, UINT, UINT, ID3DBlob**, ID3DBlob**);
+
+  const auto fnc_compile = reinterpret_cast<PFN_D3DCOMPILE>(
+      GetProcAddress(lib_compiler_, "D3DCompile"));
+  if (!fnc_compile) {
+    return nullptr;
+  }
+
+  DWORD flags = D3DCOMPILE_ENABLE_STRICTNESS;
+
+#if defined(NDEBUG)
+// flags |= D3DCOMPILE_OPTIMIZATION_LEVEL3;
+// flags |= D3DCOMPILE_AVOID_FLOW_CONTROL;
+#else
+  flags |= D3DCOMPILE_DEBUG;
+  flags |= D3DCOMPILE_SKIP_OPTIMIZATION;
+#endif
+
+  ID3DBlob* blob = nullptr;
+  ID3DBlob* blob_err = nullptr;
+
+  const auto psrc = source_code.c_str();
+  const auto len = source_code.size() + 1;
+
+  const auto hr =
+      fnc_compile(psrc, len, nullptr, nullptr, nullptr, entry_point.c_str(),
+                  model.c_str(), flags, 0, &blob, &blob_err);
+
+  if (FAILED(hr)) {
+    if (blob_err) {
+      // TODO: Log the error.
+      blob_err->Release();
+    }
+    return nullptr;
+  }
+
+  if (blob_err) {
+    blob_err->Release();
+  }
+
+  return std::shared_ptr<ID3DBlob>(blob, [](ID3DBlob* p) {
+    if (p)
+      p->Release();
+  });
+}
+
+std::shared_ptr<Effect> Device::create_default_effect() {
+  const auto vsh =
+      R"--(struct VS_INPUT
+{
+	float4 pos : POSITION;
+	float2 tex : TEXCOORD0;
+};
+
+struct VS_OUTPUT
+{
+	float4 pos : SV_POSITION;
+	float2 tex : TEXCOORD0;
+};
+
+VS_OUTPUT main(VS_INPUT input)
+{
+	VS_OUTPUT output;
+	output.pos = input.pos;
+	output.tex = input.tex;
+	return output;
+})--";
+
+  const auto psh =
+      R"--(Texture2D tex0 : register(t0);
+SamplerState samp0 : register(s0);
+
+struct VS_OUTPUT
+{
+	float4 pos : SV_POSITION;
+	float2 tex : TEXCOORD0;
+};
+
+float4 main(VS_OUTPUT input) : SV_Target
+{
+	return tex0.Sample(samp0, input.tex);
+})--";
+
+  return create_effect(vsh, "main", "vs_4_0", psh, "main", "ps_4_0");
+}
+
+std::shared_ptr<Effect> Device::create_effect(const std::string& vertex_code,
+                                              const std::string& vertex_entry,
+                                              const std::string& vertex_model,
+                                              const std::string& pixel_code,
+                                              const std::string& pixel_entry,
+                                              const std::string& pixel_model) {
+  const auto vs_blob = compile_shader(vertex_code, vertex_entry, vertex_model);
+
+  ID3D11VertexShader* vshdr = nullptr;
+  ID3D11InputLayout* layout = nullptr;
+
+  if (vs_blob) {
+    device_->CreateVertexShader(vs_blob->GetBufferPointer(),
+                                vs_blob->GetBufferSize(), nullptr, &vshdr);
+
+    D3D11_INPUT_ELEMENT_DESC layout_desc[] = {
+        {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
+         D3D11_INPUT_PER_VERTEX_DATA, 0},
+        {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12,
+         D3D11_INPUT_PER_VERTEX_DATA, 0},
+    };
+
+    UINT elements = ARRAYSIZE(layout_desc);
+
+    // Create the input layout.
+    device_->CreateInputLayout(layout_desc, elements,
+                               vs_blob->GetBufferPointer(),
+                               vs_blob->GetBufferSize(), &layout);
+  }
+
+  const auto ps_blob = compile_shader(pixel_code, pixel_entry, pixel_model);
+  ID3D11PixelShader* pshdr = nullptr;
+  if (ps_blob) {
+    device_->CreatePixelShader(ps_blob->GetBufferPointer(),
+                               ps_blob->GetBufferSize(), nullptr, &pshdr);
+  }
+
+  return std::make_shared<Effect>(vshdr, pshdr, layout);
+}
+
+Layer::Layer(const std::shared_ptr<Device>& device, bool flip)
+    : device_(device), flip_(flip) {
+  bounds_.x = bounds_.y = bounds_.width = bounds_.height = 0.0f;
+}
+
+Layer::~Layer() {}
+
+void Layer::attach(const std::shared_ptr<Composition>& parent) {
+  composition_ = parent;
+}
+
+std::shared_ptr<Composition> Layer::composition() const {
+  return composition_.lock();
+}
+
+Rect Layer::bounds() const {
+  return bounds_;
+}
+
+void Layer::move(float x, float y, float width, float height) {
+  bounds_.x = x;
+  bounds_.y = y;
+  bounds_.width = width;
+  bounds_.height = height;
+
+  // It's not efficient to create the quad everytime we move, but for now we're
+  // just trying to get something on-screen.
+  geometry_.reset();
+}
+
+void Layer::tick(double) {
+  // Nothing to update in the base class.
+}
+
+void Layer::render_texture(const std::shared_ptr<Context>& ctx,
+                           const std::shared_ptr<Texture2D>& texture) {
+  if (!geometry_) {
+    geometry_ = device_->create_quad(bounds_.x, bounds_.y, bounds_.width,
+                                     bounds_.height, flip_);
+  }
+
+  if (geometry_ && texture) {
+    // We need a shader.
+    if (!effect_) {
+      effect_ = device_->create_default_effect();
+    }
+
+    // Bind our states/resource to the pipeline.
+    ScopedBinder<Geometry> quad_binder(ctx, geometry_);
+    ScopedBinder<Effect> fx_binder(ctx, effect_);
+    ScopedBinder<Texture2D> tex_binder(ctx, texture);
+
+    // Draw the quad.
+    geometry_->draw();
+  }
+}
+
+Composition::Composition(const std::shared_ptr<Device>& device,
+                         int width,
+                         int height)
+    : width_(width), height_(height), vsync_(true), device_(device) {
+  fps_ = 0.0;
+  time_ = 0.0;
+  frame_ = 0;
+  fps_start_ = GetTimeNow();
+}
+
+bool Composition::is_vsync() const {
+  return vsync_;
+}
+
+double Composition::time() const {
+  return time_;
+}
+
+double Composition::fps() const {
+  return fps_;
+}
+
+void Composition::add_layer(const std::shared_ptr<Layer>& layer) {
+  if (layer) {
+    layers_.push_back(layer);
+
+    // Attach ourselves as the parent.
+    layer->attach(shared_from_this());
+  }
+}
+
+bool Composition::remove_layer(const std::shared_ptr<Layer>& layer) {
+  size_t match = 0;
+  if (layer) {
+    for (auto i = layers_.begin(); i != layers_.end();) {
+      if ((*i).get() == layer.get()) {
+        i = layers_.erase(i);
+        match++;
+      } else {
+        i++;
+      }
+    }
+  }
+  return (match > 0);
+}
+
+void Composition::resize(bool vsync, int width, int height) {
+  vsync_ = vsync;
+  width_ = width;
+  height_ = height;
+}
+
+void Composition::tick(double t) {
+  time_ = t;
+  for (const auto& layer : layers_) {
+    layer->tick(t);
+  }
+}
+
+void Composition::render(const std::shared_ptr<Context>& ctx) {
+  // Use painter's algorithm and render our layers in order (not doing any dept
+  // or 3D here).
+  for (const auto& layer : layers_) {
+    layer->render(ctx);
+  }
+
+  frame_++;
+  const auto now = GetTimeNow();
+  if ((now - fps_start_) > 1000000) {
+    fps_ = frame_ / double((now - fps_start_) / 1000000.0);
+    frame_ = 0;
+    fps_start_ = now;
+  }
+}
+
+FrameBuffer::FrameBuffer(const std::shared_ptr<Device>& device)
+    : device_(device) {}
+
+void FrameBuffer::on_paint(void* shared_handle) {
+  // Did the shared texture change?
+  if (shared_buffer_ && shared_handle != shared_buffer_->share_handle()) {
+    shared_buffer_.reset();
+  }
+
+  // Open the shared texture.
+  if (!shared_buffer_) {
+    shared_buffer_ = device_->open_shared_texture((void*)shared_handle);
+    if (!shared_buffer_) {
+      LOG(ERROR) << "d3d11: Could not open shared texture!";
+    }
+  }
+}
+
+}  // namespace d3d11
+}  // namespace client
\ No newline at end of file
diff --git tests/cefclient/browser/osr_d3d11_win.h tests/cefclient/browser/osr_d3d11_win.h
new file mode 100644
index 00000000..f1190e38
--- /dev/null
+++ tests/cefclient/browser/osr_d3d11_win.h
@@ -0,0 +1,330 @@
+// Copyright 2018 The Chromium Embedded Framework Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be found
+// in the LICENSE file.
+//
+// Portions Copyright (c) 2018 Daktronics with the following MIT License:
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+
+#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_D3D11_WIN_H_
+#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_D3D11_WIN_H_
+#pragma once
+
+#include <d3d11_1.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "include/base/cef_macros.h"
+
+namespace client {
+namespace d3d11 {
+
+class Composition;
+class Context;
+class Effect;
+class Geometry;
+class SwapChain;
+class Texture2D;
+
+// Basic rect for floats.
+struct Rect {
+  float x;
+  float y;
+  float width;
+  float height;
+};
+
+template <class T>
+class ScopedBinder {
+ public:
+  ScopedBinder(const std::shared_ptr<Context>& ctx,
+               const std::shared_ptr<T>& target)
+      : target_(target) {
+    if (target_) {
+      target_->bind(ctx);
+    }
+  }
+  ~ScopedBinder() {
+    if (target_) {
+      target_->unbind();
+    }
+  }
+
+ private:
+  const std::shared_ptr<T> target_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedBinder);
+};
+
+class Context {
+ public:
+  Context(ID3D11DeviceContext*);
+
+  void flush();
+
+  operator ID3D11DeviceContext*() { return ctx_.get(); }
+
+ private:
+  const std::shared_ptr<ID3D11DeviceContext> ctx_;
+};
+
+// Encapsulate a D3D11 Device object.
+class Device {
+ public:
+  Device(ID3D11Device*, ID3D11DeviceContext*);
+
+  static std::shared_ptr<Device> create();
+
+  std::string adapter_name() const;
+
+  operator ID3D11Device*() { return device_.get(); }
+
+  std::shared_ptr<Context> immedidate_context();
+
+  std::shared_ptr<SwapChain> create_swapchain(HWND,
+                                              int width = 0,
+                                              int height = 0);
+
+  std::shared_ptr<Geometry> create_quad(float x,
+                                        float y,
+                                        float width,
+                                        float height,
+                                        bool flip = false);
+
+  std::shared_ptr<Texture2D> create_texture(int width,
+                                            int height,
+                                            DXGI_FORMAT format,
+                                            const void* data,
+                                            size_t row_stride);
+
+  std::shared_ptr<Texture2D> open_shared_texture(void*);
+
+  // Create some basic shaders so we can draw a textured-quad.
+  std::shared_ptr<Effect> create_default_effect();
+
+  std::shared_ptr<Effect> create_effect(const std::string& vertex_code,
+                                        const std::string& vertex_entry,
+                                        const std::string& vertex_model,
+                                        const std::string& pixel_code,
+                                        const std::string& pixel_entry,
+                                        const std::string& pixel_model);
+
+ private:
+  std::shared_ptr<ID3DBlob> compile_shader(const std::string& source_code,
+                                           const std::string& entry_point,
+                                           const std::string& model);
+
+  HMODULE lib_compiler_;
+
+  const std::shared_ptr<ID3D11Device> device_;
+  const std::shared_ptr<Context> ctx_;
+
+  DISALLOW_COPY_AND_ASSIGN(Device);
+};
+
+// Encapsulate a DXGI swapchain for a window.
+class SwapChain {
+ public:
+  SwapChain(IDXGISwapChain*,
+            ID3D11RenderTargetView*,
+            ID3D11SamplerState*,
+            ID3D11BlendState*);
+
+  void bind(const std::shared_ptr<Context>& ctx);
+  void unbind();
+
+  void clear(float red, float green, float blue, float alpha);
+
+  void present(int sync_interval);
+  void resize(int width, int height);
+
+  int width() const { return width_; }
+  int height() const { return height_; }
+
+ private:
+  const std::shared_ptr<ID3D11SamplerState> sampler_;
+  const std::shared_ptr<ID3D11BlendState> blender_;
+  const std::shared_ptr<IDXGISwapChain> swapchain_;
+  std::shared_ptr<ID3D11RenderTargetView> rtv_;
+  std::shared_ptr<Context> ctx_;
+  int width_;
+  int height_;
+
+  DISALLOW_COPY_AND_ASSIGN(SwapChain);
+};
+
+class Texture2D {
+ public:
+  Texture2D(ID3D11Texture2D* tex, ID3D11ShaderResourceView* srv);
+
+  void bind(std::shared_ptr<Context> const& ctx);
+  void unbind();
+
+  uint32_t width() const;
+  uint32_t height() const;
+  DXGI_FORMAT format() const;
+
+  bool has_mutex() const;
+
+  bool lock_key(uint64_t key, uint32_t timeout_ms);
+  void unlock_key(uint64_t key);
+
+  void* share_handle() const;
+
+  void copy_from(const std::shared_ptr<Texture2D>&);
+
+ private:
+  HANDLE share_handle_;
+
+  const std::shared_ptr<ID3D11Texture2D> texture_;
+  const std::shared_ptr<ID3D11ShaderResourceView> srv_;
+  std::shared_ptr<IDXGIKeyedMutex> keyed_mutex_;
+  std::shared_ptr<Context> ctx_;
+
+  DISALLOW_COPY_AND_ASSIGN(Texture2D);
+};
+
+class Effect {
+ public:
+  Effect(ID3D11VertexShader* vsh,
+         ID3D11PixelShader* psh,
+         ID3D11InputLayout* layout);
+
+  void bind(const std::shared_ptr<Context>& ctx);
+  void unbind();
+
+ private:
+  const std::shared_ptr<ID3D11VertexShader> vsh_;
+  const std::shared_ptr<ID3D11PixelShader> psh_;
+  const std::shared_ptr<ID3D11InputLayout> layout_;
+  std::shared_ptr<Context> ctx_;
+
+  DISALLOW_COPY_AND_ASSIGN(Effect);
+};
+
+class Geometry {
+ public:
+  Geometry(D3D_PRIMITIVE_TOPOLOGY primitive,
+           uint32_t vertices,
+           uint32_t stride,
+           ID3D11Buffer*);
+
+  void bind(const std::shared_ptr<Context>& ctx);
+  void unbind();
+
+  void draw();
+
+ private:
+  D3D_PRIMITIVE_TOPOLOGY primitive_;
+  uint32_t vertices_;
+  uint32_t stride_;
+  const std::shared_ptr<ID3D11Buffer> buffer_;
+  std::shared_ptr<Context> ctx_;
+
+  DISALLOW_COPY_AND_ASSIGN(Geometry);
+};
+
+// Abstraction for a 2D layer within a composition.
+class Layer {
+ public:
+  Layer(const std::shared_ptr<Device>& device, bool flip);
+  virtual ~Layer();
+
+  void attach(const std::shared_ptr<Composition>&);
+
+  // Uses normalized 0-1.0 coordinates.
+  virtual void move(float x, float y, float width, float height);
+
+  virtual void tick(double t);
+  virtual void render(const std::shared_ptr<Context>& ctx) = 0;
+
+  Rect bounds() const;
+
+  std::shared_ptr<Composition> composition() const;
+
+ protected:
+  // Helper method for derived classes to draw a textured-quad.
+  void render_texture(const std::shared_ptr<Context>& ctx,
+                      const std::shared_ptr<Texture2D>& texture);
+
+  const std::shared_ptr<Device> device_;
+  const bool flip_;
+
+  Rect bounds_;
+  std::shared_ptr<Geometry> geometry_;
+  std::shared_ptr<Effect> effect_;
+
+ private:
+  std::weak_ptr<Composition> composition_;
+
+  DISALLOW_COPY_AND_ASSIGN(Layer);
+};
+
+// A collection of layers. Will render 1-N layers to a D3D11 device.
+class Composition : public std::enable_shared_from_this<Composition> {
+ public:
+  Composition(const std::shared_ptr<Device>& device,
+              int width = 0,
+              int height = 0);
+
+  int width() const { return width_; }
+  int height() const { return height_; }
+
+  double fps() const;
+  double time() const;
+
+  bool is_vsync() const;
+
+  void tick(double);
+  void render(const std::shared_ptr<Context>&);
+
+  void add_layer(const std::shared_ptr<Layer>& layer);
+  bool remove_layer(const std::shared_ptr<Layer>& layer);
+  void resize(bool vsync, int width, int height);
+
+ private:
+  int width_;
+  int height_;
+  uint32_t frame_;
+  int64_t fps_start_;
+  double fps_;
+  double time_;
+  bool vsync_;
+
+  const std::shared_ptr<Device> device_;
+  std::vector<std::shared_ptr<Layer>> layers_;
+
+  DISALLOW_COPY_AND_ASSIGN(Composition);
+};
+
+class FrameBuffer {
+ public:
+  explicit FrameBuffer(const std::shared_ptr<Device>& device);
+
+  // Called in response to CEF's OnAcceleratedPaint notification.
+  void on_paint(void* shared_handle);
+
+  // Returns what should be considered the front buffer.
+  std::shared_ptr<Texture2D> texture() const { return shared_buffer_; }
+
+ private:
+  const std::shared_ptr<Device> device_;
+  std::shared_ptr<Texture2D> shared_buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(FrameBuffer);
+};
+
+}  // namespace d3d11
+}  // namespace client
+
+#endif  // CEF_TESTS_CEFCLIENT_BROWSER_OSR_D3D11_WIN_H_
diff --git tests/cefclient/browser/osr_render_handler_win.cc tests/cefclient/browser/osr_render_handler_win.cc
new file mode 100644
index 00000000..8fa9b169
--- /dev/null
+++ tests/cefclient/browser/osr_render_handler_win.cc
@@ -0,0 +1,83 @@
+// Copyright 2018 The Chromium Embedded Framework Authors. All rights
+// reserved. Use of this source code is governed by a BSD-style license that
+// can be found in the LICENSE file.
+
+#include "tests/cefclient/browser/osr_render_handler_win.h"
+
+#include "include/base/cef_bind.h"
+#include "include/wrapper/cef_closure_task.h"
+#include "include/wrapper/cef_helpers.h"
+#include "tests/shared/browser/util_win.h"
+
+namespace client {
+
+OsrRenderHandlerWin::OsrRenderHandlerWin(const OsrRendererSettings& settings,
+                                         HWND hwnd)
+    : settings_(settings),
+      hwnd_(hwnd),
+      begin_frame_pending_(false),
+      weak_factory_(this) {
+  CEF_REQUIRE_UI_THREAD();
+  DCHECK(hwnd_);
+}
+
+OsrRenderHandlerWin::~OsrRenderHandlerWin() {
+  CEF_REQUIRE_UI_THREAD();
+}
+
+void OsrRenderHandlerWin::SetBrowser(CefRefPtr<CefBrowser> browser) {
+  CEF_REQUIRE_UI_THREAD();
+  browser_ = browser;
+  if (browser_ && settings_.external_begin_frame_enabled) {
+    // Start the BeginFrame timer.
+    Invalidate();
+  }
+}
+
+void OsrRenderHandlerWin::Invalidate() {
+  CEF_REQUIRE_UI_THREAD();
+  if (begin_frame_pending_) {
+    // The timer is already running.
+    return;
+  }
+
+  // Trigger the BeginFrame timer.
+  CHECK_GT(settings_.begin_frame_rate, 0);
+  const float delay_us = (1.0 / double(settings_.begin_frame_rate)) * 1000000.0;
+  TriggerBeginFrame(0, delay_us);
+}
+
+void OsrRenderHandlerWin::TriggerBeginFrame(uint64_t last_time_us,
+                                            float delay_us) {
+  if (begin_frame_pending_ && !settings_.external_begin_frame_enabled) {
+    // Render immediately and then wait for the next call to Invalidate() or
+    // On[Accelerated]Paint().
+    begin_frame_pending_ = false;
+    Render();
+    return;
+  }
+
+  const auto now = GetTimeNow();
+  float offset = now - last_time_us;
+  if (offset > delay_us) {
+    offset = delay_us;
+  }
+
+  if (!begin_frame_pending_) {
+    begin_frame_pending_ = true;
+  }
+
+  // Trigger again after the necessary delay to maintain the desired frame rate.
+  CefPostDelayedTask(TID_UI,
+                     base::Bind(&OsrRenderHandlerWin::TriggerBeginFrame,
+                                weak_factory_.GetWeakPtr(), now, delay_us),
+                     int64(offset / 1000.0));
+
+  if (settings_.external_begin_frame_enabled && browser_) {
+    // We're running the BeginFrame timer. Trigger rendering via
+    // On[Accelerated]Paint().
+    browser_->GetHost()->SendExternalBeginFrame();
+  }
+}
+
+}  // namespace client
diff --git tests/cefclient/browser/osr_render_handler_win.h tests/cefclient/browser/osr_render_handler_win.h
new file mode 100644
index 00000000..4eee0249
--- /dev/null
+++ tests/cefclient/browser/osr_render_handler_win.h
@@ -0,0 +1,82 @@
+// Copyright 2018 The Chromium Embedded Framework Authors. All rights
+// reserved. Use of this source code is governed by a BSD-style license that
+// can be found in the LICENSE file.
+
+#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDER_HANDLER_WIN_H_
+#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDER_HANDLER_WIN_H_
+#pragma once
+
+#include "include/base/cef_weak_ptr.h"
+#include "include/cef_render_handler.h"
+#include "tests/cefclient/browser/osr_renderer_settings.h"
+
+namespace client {
+
+// Abstract base class for implementing OSR rendering with different backends on
+// Windows. Methods are only called on the UI thread.
+class OsrRenderHandlerWin {
+ public:
+  OsrRenderHandlerWin(const OsrRendererSettings& settings, HWND hwnd);
+  virtual ~OsrRenderHandlerWin();
+
+  void SetBrowser(CefRefPtr<CefBrowser> browser);
+
+  // Rotate the texture based on mouse events.
+  virtual void SetSpin(float spinX, float spinY) = 0;
+  virtual void IncrementSpin(float spinDX, float spinDY) = 0;
+
+  // Popup hit testing.
+  virtual bool IsOverPopupWidget(int x, int y) const = 0;
+  virtual int GetPopupXOffset() const = 0;
+  virtual int GetPopupYOffset() const = 0;
+
+  // CefRenderHandler callbacks.
+  virtual void OnPopupShow(CefRefPtr<CefBrowser> browser, bool show) = 0;
+  // |rect| must be in pixel coordinates.
+  virtual void OnPopupSize(CefRefPtr<CefBrowser> browser,
+                           const CefRect& rect) = 0;
+
+  // Used when not rendering with shared textures.
+  virtual void OnPaint(CefRefPtr<CefBrowser> browser,
+                       CefRenderHandler::PaintElementType type,
+                       const CefRenderHandler::RectList& dirtyRects,
+                       const void* buffer,
+                       int width,
+                       int height) = 0;
+
+  // Used when rendering with shared textures.
+  virtual void OnAcceleratedPaint(CefRefPtr<CefBrowser> browser,
+                                  CefRenderHandler::PaintElementType type,
+                                  const CefRenderHandler::RectList& dirtyRects,
+                                  void* share_handle) = 0;
+
+  bool send_begin_frame() const {
+    return settings_.external_begin_frame_enabled;
+  }
+  HWND hwnd() const { return hwnd_; }
+
+ protected:
+  // Called to trigger the BeginFrame timer.
+  void Invalidate();
+
+  // Called by the BeginFrame timer.
+  virtual void Render() = 0;
+
+ private:
+  void TriggerBeginFrame(uint64_t last_time_us, float delay_us);
+
+  // The below members are only accessed on the UI thread.
+  const OsrRendererSettings settings_;
+  const HWND hwnd_;
+  bool begin_frame_pending_;
+  CefRefPtr<CefBrowser> browser_;
+
+  // Must be the last member.
+  base::WeakPtrFactory<OsrRenderHandlerWin> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(OsrRenderHandlerWin);
+};
+
+}  // namespace client
+
+#endif  // CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDER_HANDLER_WIN_H_
diff --git tests/cefclient/browser/osr_render_handler_win_d3d11.cc tests/cefclient/browser/osr_render_handler_win_d3d11.cc
new file mode 100644
index 00000000..bee90359
--- /dev/null
+++ tests/cefclient/browser/osr_render_handler_win_d3d11.cc
@@ -0,0 +1,218 @@
+// Copyright 2018 The Chromium Embedded Framework Authors. All rights
+// reserved. Use of this source code is governed by a BSD-style license that
+// can be found in the LICENSE file.
+
+#include "tests/cefclient/browser/osr_render_handler_win_d3d11.h"
+
+#include "include/base/cef_bind.h"
+#include "include/wrapper/cef_closure_task.h"
+#include "include/wrapper/cef_helpers.h"
+#include "tests/cefclient/browser/main_context.h"
+#include "tests/shared/browser/util_win.h"
+
+namespace client {
+
+BrowserLayer::BrowserLayer(const std::shared_ptr<d3d11::Device>& device)
+    : d3d11::Layer(device, true /* flip */) {
+  frame_buffer_ = std::make_shared<d3d11::FrameBuffer>(device_);
+}
+
+void BrowserLayer::render(const std::shared_ptr<d3d11::Context>& ctx) {
+  // Use the base class method to draw our texture.
+  render_texture(ctx, frame_buffer_->texture());
+}
+
+void BrowserLayer::on_paint(void* share_handle) {
+  frame_buffer_->on_paint(share_handle);
+}
+
+std::pair<uint32_t, uint32_t> BrowserLayer::texture_size() const {
+  const auto texture = frame_buffer_->texture();
+  return std::make_pair(texture->width(), texture->height());
+}
+
+PopupLayer::PopupLayer(const std::shared_ptr<d3d11::Device>& device)
+    : BrowserLayer(device) {}
+
+void PopupLayer::set_bounds(const CefRect& bounds) {
+  const auto comp = composition();
+  if (!comp)
+    return;
+
+  const auto outer_width = comp->width();
+  const auto outer_height = comp->height();
+  if (outer_width == 0 || outer_height == 0)
+    return;
+
+  original_bounds_ = bounds;
+  bounds_ = bounds;
+
+  // If x or y are negative, move them to 0.
+  if (bounds_.x < 0)
+    bounds_.x = 0;
+  if (bounds_.y < 0)
+    bounds_.y = 0;
+  // If popup goes outside the view, try to reposition origin
+  if (bounds_.x + bounds_.width > outer_width)
+    bounds_.x = outer_width - bounds_.width;
+  if (bounds_.y + bounds_.height > outer_height)
+    bounds_.y = outer_height - bounds_.height;
+  // If x or y became negative, move them to 0 again.
+  if (bounds_.x < 0)
+    bounds_.x = 0;
+  if (bounds_.y < 0)
+    bounds_.y = 0;
+
+  const auto x = bounds_.x / float(outer_width);
+  const auto y = bounds_.y / float(outer_height);
+  const auto w = bounds_.width / float(outer_width);
+  const auto h = bounds_.height / float(outer_height);
+  move(x, y, w, h);
+}
+
+OsrRenderHandlerWinD3D11::OsrRenderHandlerWinD3D11(
+    const OsrRendererSettings& settings,
+    HWND hwnd)
+    : OsrRenderHandlerWin(settings, hwnd), start_time_(0) {}
+
+bool OsrRenderHandlerWinD3D11::Initialize(CefRefPtr<CefBrowser> browser,
+                                          int width,
+                                          int height) {
+  CEF_REQUIRE_UI_THREAD();
+
+  // Retrieve the shared D3D11 device instance.
+  device_ = MainContext::Get()->GetD3D11Device();
+  DCHECK(device_);
+  if (!device_)
+    return false;
+
+  // Create a D3D11 swapchain for the window.
+  swap_chain_ = device_->create_swapchain(hwnd());
+  DCHECK(swap_chain_);
+  if (!swap_chain_)
+    return false;
+
+  // Create the browser layer.
+  browser_layer_ = std::make_shared<BrowserLayer>(device_);
+
+  // Set up the composition.
+  composition_ = std::make_shared<d3d11::Composition>(device_, width, height);
+  composition_->add_layer(browser_layer_);
+
+  // Size to the whole composition.
+  browser_layer_->move(0.0f, 0.0f, 1.0f, 1.0f);
+
+  start_time_ = GetTimeNow();
+
+  SetBrowser(browser);
+  return true;
+}
+
+void OsrRenderHandlerWinD3D11::SetSpin(float spinX, float spinY) {
+  CEF_REQUIRE_UI_THREAD();
+  // Spin support is not implemented.
+}
+
+void OsrRenderHandlerWinD3D11::IncrementSpin(float spinDX, float spinDY) {
+  CEF_REQUIRE_UI_THREAD();
+  // Spin support is not implemented.
+}
+
+bool OsrRenderHandlerWinD3D11::IsOverPopupWidget(int x, int y) const {
+  CEF_REQUIRE_UI_THREAD();
+  return popup_layer_ && popup_layer_->contains(x, y);
+}
+
+int OsrRenderHandlerWinD3D11::GetPopupXOffset() const {
+  CEF_REQUIRE_UI_THREAD();
+  if (popup_layer_)
+    return popup_layer_->xoffset();
+  return 0;
+}
+
+int OsrRenderHandlerWinD3D11::GetPopupYOffset() const {
+  CEF_REQUIRE_UI_THREAD();
+  if (popup_layer_)
+    return popup_layer_->yoffset();
+  return 0;
+}
+
+void OsrRenderHandlerWinD3D11::OnPopupShow(CefRefPtr<CefBrowser> browser,
+                                           bool show) {
+  CEF_REQUIRE_UI_THREAD();
+
+  if (show) {
+    DCHECK(!popup_layer_);
+
+    // Create a new layer.
+    popup_layer_ = std::make_shared<PopupLayer>(device_);
+    composition_->add_layer(popup_layer_);
+  } else {
+    DCHECK(popup_layer_);
+
+    composition_->remove_layer(popup_layer_);
+    popup_layer_ = nullptr;
+
+    Render();
+  }
+}
+
+void OsrRenderHandlerWinD3D11::OnPopupSize(CefRefPtr<CefBrowser> browser,
+                                           const CefRect& rect) {
+  CEF_REQUIRE_UI_THREAD();
+  popup_layer_->set_bounds(rect);
+}
+
+void OsrRenderHandlerWinD3D11::OnPaint(
+    CefRefPtr<CefBrowser> browser,
+    CefRenderHandler::PaintElementType type,
+    const CefRenderHandler::RectList& dirtyRects,
+    const void* buffer,
+    int width,
+    int height) {
+  // Not used with this implementation.
+  NOTREACHED();
+}
+
+void OsrRenderHandlerWinD3D11::OnAcceleratedPaint(
+    CefRefPtr<CefBrowser> browser,
+    CefRenderHandler::PaintElementType type,
+    const CefRenderHandler::RectList& dirtyRects,
+    void* share_handle) {
+  CEF_REQUIRE_UI_THREAD();
+
+  if (type == PET_POPUP) {
+    popup_layer_->on_paint(share_handle);
+  } else {
+    browser_layer_->on_paint(share_handle);
+  }
+
+  Render();
+}
+
+void OsrRenderHandlerWinD3D11::Render() {
+  // Update composition + layers based on time.
+  const auto t = (GetTimeNow() - start_time_) / 1000000.0;
+  composition_->tick(t);
+
+  auto ctx = device_->immedidate_context();
+  swap_chain_->bind(ctx);
+
+  const auto texture_size = browser_layer_->texture_size();
+
+  // Resize the composition and swap chain to match the texture if necessary.
+  composition_->resize(!send_begin_frame(), texture_size.first,
+                       texture_size.second);
+  swap_chain_->resize(texture_size.first, texture_size.second);
+
+  // Clear the render target.
+  swap_chain_->clear(0.0f, 0.0f, 1.0f, 1.0f);
+
+  // Render the scene.
+  composition_->render(ctx);
+
+  // Present to window.
+  swap_chain_->present(send_begin_frame() ? 0 : 1);
+}
+
+}  // namespace client
diff --git tests/cefclient/browser/osr_render_handler_win_d3d11.h tests/cefclient/browser/osr_render_handler_win_d3d11.h
new file mode 100644
index 00000000..a72b1dda
--- /dev/null
+++ tests/cefclient/browser/osr_render_handler_win_d3d11.h
@@ -0,0 +1,90 @@
+// Copyright 2018 The Chromium Embedded Framework Authors. All rights
+// reserved. Use of this source code is governed by a BSD-style license that
+// can be found in the LICENSE file.
+
+#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDER_HANDLER_WIN_D3D11_H_
+#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDER_HANDLER_WIN_D3D11_H_
+#pragma once
+
+#include "tests/cefclient/browser/osr_d3d11_win.h"
+#include "tests/cefclient/browser/osr_render_handler_win.h"
+#include "tests/cefclient/browser/osr_renderer_settings.h"
+
+namespace client {
+
+class BrowserLayer : public d3d11::Layer {
+ public:
+  explicit BrowserLayer(const std::shared_ptr<d3d11::Device>& device);
+
+  void render(const std::shared_ptr<d3d11::Context>& ctx) OVERRIDE;
+
+  void on_paint(void* share_handle);
+
+  // After calling on_paint() we can query the texture size.
+  std::pair<uint32_t, uint32_t> texture_size() const;
+
+ private:
+  std::shared_ptr<d3d11::FrameBuffer> frame_buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(BrowserLayer);
+};
+
+class PopupLayer : public BrowserLayer {
+ public:
+  explicit PopupLayer(const std::shared_ptr<d3d11::Device>& device);
+
+  void set_bounds(const CefRect& bounds);
+
+  bool contains(int x, int y) const { return bounds_.Contains(x, y); }
+  int xoffset() const { return original_bounds_.x - bounds_.x; }
+  int yoffset() const { return original_bounds_.y - bounds_.y; }
+
+ private:
+  CefRect original_bounds_;
+  CefRect bounds_;
+
+  DISALLOW_COPY_AND_ASSIGN(PopupLayer);
+};
+
+class OsrRenderHandlerWinD3D11 : public OsrRenderHandlerWin {
+ public:
+  OsrRenderHandlerWinD3D11(const OsrRendererSettings& settings, HWND hwnd);
+
+  // Must be called immediately after object creation.
+  // May fail if D3D11 cannot be initialized.
+  bool Initialize(CefRefPtr<CefBrowser> browser, int width, int height);
+
+  void SetSpin(float spinX, float spinY) OVERRIDE;
+  void IncrementSpin(float spinDX, float spinDY) OVERRIDE;
+  bool IsOverPopupWidget(int x, int y) const OVERRIDE;
+  int GetPopupXOffset() const OVERRIDE;
+  int GetPopupYOffset() const OVERRIDE;
+  void OnPopupShow(CefRefPtr<CefBrowser> browser, bool show) OVERRIDE;
+  void OnPopupSize(CefRefPtr<CefBrowser> browser, const CefRect& rect) OVERRIDE;
+  void OnPaint(CefRefPtr<CefBrowser> browser,
+               CefRenderHandler::PaintElementType type,
+               const CefRenderHandler::RectList& dirtyRects,
+               const void* buffer,
+               int width,
+               int height) OVERRIDE;
+  void OnAcceleratedPaint(CefRefPtr<CefBrowser> browser,
+                          CefRenderHandler::PaintElementType type,
+                          const CefRenderHandler::RectList& dirtyRects,
+                          void* share_handle) OVERRIDE;
+
+ private:
+  void Render() OVERRIDE;
+
+  uint64_t start_time_;
+  std::shared_ptr<d3d11::Device> device_;
+  std::shared_ptr<d3d11::SwapChain> swap_chain_;
+  std::shared_ptr<d3d11::Composition> composition_;
+  std::shared_ptr<BrowserLayer> browser_layer_;
+  std::shared_ptr<PopupLayer> popup_layer_;
+
+  DISALLOW_COPY_AND_ASSIGN(OsrRenderHandlerWinD3D11);
+};
+
+}  // namespace client
+
+#endif  // CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDER_HANDLER_WIN_D3D11_H_
diff --git tests/cefclient/browser/osr_render_handler_win_gl.cc tests/cefclient/browser/osr_render_handler_win_gl.cc
new file mode 100644
index 00000000..6bfe1ea3
--- /dev/null
+++ tests/cefclient/browser/osr_render_handler_win_gl.cc
@@ -0,0 +1,201 @@
+// Copyright 2018 The Chromium Embedded Framework Authors. All rights
+// reserved. Use of this source code is governed by a BSD-style license that
+// can be found in the LICENSE file.
+
+#include "tests/cefclient/browser/osr_render_handler_win_gl.h"
+
+#include "include/base/cef_bind.h"
+#include "include/wrapper/cef_closure_task.h"
+#include "include/wrapper/cef_helpers.h"
+#include "tests/shared/browser/util_win.h"
+
+namespace client {
+
+namespace {
+
+// Helper that calls wglMakeCurrent.
+class ScopedGLContext {
+ public:
+  ScopedGLContext(HDC hdc, HGLRC hglrc, bool swap_buffers)
+      : hdc_(hdc), swap_buffers_(swap_buffers) {
+    BOOL result = wglMakeCurrent(hdc, hglrc);
+    ALLOW_UNUSED_LOCAL(result);
+    DCHECK(result);
+  }
+  ~ScopedGLContext() {
+    BOOL result = wglMakeCurrent(NULL, NULL);
+    DCHECK(result);
+    if (swap_buffers_) {
+      result = SwapBuffers(hdc_);
+      DCHECK(result);
+    }
+  }
+
+ private:
+  const HDC hdc_;
+  const bool swap_buffers_;
+};
+
+}  // namespace
+
+OsrRenderHandlerWinGL::OsrRenderHandlerWinGL(
+    const OsrRendererSettings& settings,
+    HWND hwnd)
+    : OsrRenderHandlerWin(settings, hwnd),
+      renderer_(settings),
+      hdc_(NULL),
+      hrc_(NULL),
+      painting_popup_(false) {}
+
+void OsrRenderHandlerWinGL::Initialize(CefRefPtr<CefBrowser> browser) {
+  CEF_REQUIRE_UI_THREAD();
+  SetBrowser(browser);
+}
+
+OsrRenderHandlerWinGL::~OsrRenderHandlerWinGL() {
+  CEF_REQUIRE_UI_THREAD();
+  DisableGL();
+}
+
+void OsrRenderHandlerWinGL::SetSpin(float spinX, float spinY) {
+  CEF_REQUIRE_UI_THREAD();
+  renderer_.SetSpin(spinX, spinY);
+  Invalidate();
+}
+
+void OsrRenderHandlerWinGL::IncrementSpin(float spinDX, float spinDY) {
+  CEF_REQUIRE_UI_THREAD();
+  renderer_.IncrementSpin(spinDX, spinDY);
+  Invalidate();
+}
+
+bool OsrRenderHandlerWinGL::IsOverPopupWidget(int x, int y) const {
+  CEF_REQUIRE_UI_THREAD();
+  const CefRect& rc = renderer_.popup_rect();
+  int popup_right = rc.x + rc.width;
+  int popup_bottom = rc.y + rc.height;
+  return (x >= rc.x) && (x < popup_right) && (y >= rc.y) && (y < popup_bottom);
+}
+
+int OsrRenderHandlerWinGL::GetPopupXOffset() const {
+  CEF_REQUIRE_UI_THREAD();
+  return renderer_.original_popup_rect().x - renderer_.popup_rect().x;
+}
+
+int OsrRenderHandlerWinGL::GetPopupYOffset() const {
+  CEF_REQUIRE_UI_THREAD();
+  return renderer_.original_popup_rect().y - renderer_.popup_rect().y;
+}
+
+void OsrRenderHandlerWinGL::OnPopupShow(CefRefPtr<CefBrowser> browser,
+                                        bool show) {
+  CEF_REQUIRE_UI_THREAD();
+
+  if (!show) {
+    renderer_.ClearPopupRects();
+    browser->GetHost()->Invalidate(PET_VIEW);
+  }
+
+  renderer_.OnPopupShow(browser, show);
+}
+
+void OsrRenderHandlerWinGL::OnPopupSize(CefRefPtr<CefBrowser> browser,
+                                        const CefRect& rect) {
+  CEF_REQUIRE_UI_THREAD();
+  renderer_.OnPopupSize(browser, rect);
+}
+
+void OsrRenderHandlerWinGL::OnPaint(
+    CefRefPtr<CefBrowser> browser,
+    CefRenderHandler::PaintElementType type,
+    const CefRenderHandler::RectList& dirtyRects,
+    const void* buffer,
+    int width,
+    int height) {
+  CEF_REQUIRE_UI_THREAD();
+
+  if (painting_popup_) {
+    renderer_.OnPaint(browser, type, dirtyRects, buffer, width, height);
+    return;
+  }
+  if (!hdc_) {
+    EnableGL();
+  }
+
+  ScopedGLContext scoped_gl_context(hdc_, hrc_, true);
+  renderer_.OnPaint(browser, type, dirtyRects, buffer, width, height);
+  if (type == PET_VIEW && !renderer_.popup_rect().IsEmpty()) {
+    painting_popup_ = true;
+    browser->GetHost()->Invalidate(PET_POPUP);
+    painting_popup_ = false;
+  }
+  renderer_.Render();
+}
+
+void OsrRenderHandlerWinGL::OnAcceleratedPaint(
+    CefRefPtr<CefBrowser> browser,
+    CefRenderHandler::PaintElementType type,
+    const CefRenderHandler::RectList& dirtyRects,
+    void* share_handle) {
+  // Not used with this implementation.
+  NOTREACHED();
+}
+
+void OsrRenderHandlerWinGL::Render() {
+  if (!hdc_) {
+    EnableGL();
+  }
+
+  ScopedGLContext scoped_gl_context(hdc_, hrc_, true);
+  renderer_.Render();
+}
+
+void OsrRenderHandlerWinGL::EnableGL() {
+  PIXELFORMATDESCRIPTOR pfd;
+  int format;
+
+  // Get the device context.
+  hdc_ = GetDC(hwnd());
+
+  // Set the pixel format for the DC.
+  ZeroMemory(&pfd, sizeof(pfd));
+  pfd.nSize = sizeof(pfd);
+  pfd.nVersion = 1;
+  pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
+  pfd.iPixelType = PFD_TYPE_RGBA;
+  pfd.cColorBits = 24;
+  pfd.cDepthBits = 16;
+  pfd.iLayerType = PFD_MAIN_PLANE;
+  format = ChoosePixelFormat(hdc_, &pfd);
+  SetPixelFormat(hdc_, format, &pfd);
+
+  // Create and enable the render context.
+  hrc_ = wglCreateContext(hdc_);
+
+  ScopedGLContext scoped_gl_context(hdc_, hrc_, false);
+  renderer_.Initialize();
+}
+
+void OsrRenderHandlerWinGL::DisableGL() {
+  if (!hdc_) {
+    return;
+  }
+
+  {
+    ScopedGLContext scoped_gl_context(hdc_, hrc_, false);
+    renderer_.Cleanup();
+  }
+
+  if (IsWindow(hwnd())) {
+    // wglDeleteContext will make the context not current before deleting it.
+    BOOL result = wglDeleteContext(hrc_);
+    ALLOW_UNUSED_LOCAL(result);
+    DCHECK(result);
+    ReleaseDC(hwnd(), hdc_);
+  }
+
+  hdc_ = NULL;
+  hrc_ = NULL;
+}
+
+}  // namespace client
diff --git tests/cefclient/browser/osr_render_handler_win_gl.h tests/cefclient/browser/osr_render_handler_win_gl.h
new file mode 100644
index 00000000..606bc70e
--- /dev/null
+++ tests/cefclient/browser/osr_render_handler_win_gl.h
@@ -0,0 +1,57 @@
+// Copyright 2018 The Chromium Embedded Framework Authors. All rights
+// reserved. Use of this source code is governed by a BSD-style license that
+// can be found in the LICENSE file.
+
+#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDER_HANDLER_WIN_GL_H_
+#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDER_HANDLER_WIN_GL_H_
+#pragma once
+
+#include "tests/cefclient/browser/osr_render_handler_win.h"
+#include "tests/cefclient/browser/osr_renderer.h"
+
+namespace client {
+
+class OsrRenderHandlerWinGL : public OsrRenderHandlerWin {
+ public:
+  OsrRenderHandlerWinGL(const OsrRendererSettings& settings, HWND hwnd);
+  virtual ~OsrRenderHandlerWinGL();
+
+  // Must be called immediately after object creation.
+  void Initialize(CefRefPtr<CefBrowser> browser);
+
+  void SetSpin(float spinX, float spinY) OVERRIDE;
+  void IncrementSpin(float spinDX, float spinDY) OVERRIDE;
+  bool IsOverPopupWidget(int x, int y) const OVERRIDE;
+  int GetPopupXOffset() const OVERRIDE;
+  int GetPopupYOffset() const OVERRIDE;
+  void OnPopupShow(CefRefPtr<CefBrowser> browser, bool show) OVERRIDE;
+  void OnPopupSize(CefRefPtr<CefBrowser> browser, const CefRect& rect) OVERRIDE;
+  void OnPaint(CefRefPtr<CefBrowser> browser,
+               CefRenderHandler::PaintElementType type,
+               const CefRenderHandler::RectList& dirtyRects,
+               const void* buffer,
+               int width,
+               int height) OVERRIDE;
+  void OnAcceleratedPaint(CefRefPtr<CefBrowser> browser,
+                          CefRenderHandler::PaintElementType type,
+                          const CefRenderHandler::RectList& dirtyRects,
+                          void* share_handle) OVERRIDE;
+
+ private:
+  void Render() OVERRIDE;
+
+  void EnableGL();
+  void DisableGL();
+
+  // The below members are only accessed on the UI thread.
+  OsrRenderer renderer_;
+  HDC hdc_;
+  HGLRC hrc_;
+  bool painting_popup_;
+
+  DISALLOW_COPY_AND_ASSIGN(OsrRenderHandlerWinGL);
+};
+
+}  // namespace client
+
+#endif  // CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDER_HANDLER_WIN_GL_H_
diff --git tests/cefclient/browser/osr_renderer.cc tests/cefclient/browser/osr_renderer.cc
index 3c1cfb70..de2473af 100644
--- tests/cefclient/browser/osr_renderer.cc
+++ tests/cefclient/browser/osr_renderer.cc
@@ -40,7 +40,7 @@
 
 namespace client {
 
-OsrRenderer::OsrRenderer(const Settings& settings)
+OsrRenderer::OsrRenderer(const OsrRendererSettings& settings)
     : settings_(settings),
       initialized_(false),
       texture_id_(0),
diff --git tests/cefclient/browser/osr_renderer.h tests/cefclient/browser/osr_renderer.h
index 95d23fe0..f858d87b 100644
--- tests/cefclient/browser/osr_renderer.h
+++ tests/cefclient/browser/osr_renderer.h
@@ -8,20 +8,13 @@
 
 #include "include/cef_browser.h"
 #include "include/cef_render_handler.h"
+#include "tests/cefclient/browser/osr_renderer_settings.h"
 
 namespace client {
 
 class OsrRenderer {
  public:
-  struct Settings {
-    // If true draw a border around update rectangles.
-    bool show_update_rect;
-
-    // Background color. Enables transparency if the alpha component is 0.
-    cef_color_t background_color;
-  };
-
-  explicit OsrRenderer(const Settings& settings);
+  explicit OsrRenderer(const OsrRendererSettings& settings);
   ~OsrRenderer();
 
   // Initialize the OpenGL environment.
@@ -51,18 +44,19 @@ class OsrRenderer {
   int GetViewWidth() const { return view_width_; }
   int GetViewHeight() const { return view_height_; }
 
-  const CefRect& popup_rect() const { return popup_rect_; }
-  const CefRect& original_popup_rect() const { return original_popup_rect_; }
+  CefRect popup_rect() const { return popup_rect_; }
+  CefRect original_popup_rect() const { return original_popup_rect_; }
 
-  CefRect GetPopupRectInWebView(const CefRect& original_rect);
   void ClearPopupRects();
 
  private:
+  CefRect GetPopupRectInWebView(const CefRect& original_rect);
+
   inline bool IsTransparent() const {
     return CefColorGetA(settings_.background_color) == 0;
   };
 
-  const Settings settings_;
+  const OsrRendererSettings settings_;
   bool initialized_;
   unsigned int texture_id_;
   int view_width_;
diff --git tests/cefclient/browser/osr_renderer_settings.h tests/cefclient/browser/osr_renderer_settings.h
new file mode 100644
index 00000000..535b10a9
--- /dev/null
+++ tests/cefclient/browser/osr_renderer_settings.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 The Chromium Embedded Framework Authors. All rights
+// reserved. Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file.
+
+#ifndef CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDERER_SETTINGS_H_
+#define CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDERER_SETTINGS_H_
+#pragma once
+
+#include "include/internal/cef_types.h"
+
+namespace client {
+
+struct OsrRendererSettings {
+  OsrRendererSettings()
+      : show_update_rect(false),
+        background_color(CefColorSetARGB(255, 255, 255, 255)),
+        shared_texture_enabled(false),
+        external_begin_frame_enabled(false),
+        begin_frame_rate(0) {}
+
+  // If true draw a border around update rectangles.
+  bool show_update_rect;
+
+  // Background color. Enables transparency if the alpha component is 0.
+  cef_color_t background_color;
+
+  // Render using shared textures. Supported on Windows only via D3D11.
+  bool shared_texture_enabled;
+
+  // Client implements a BeginFrame timer by calling
+  // CefBrowserHost::SendExternalBeginFrame at the specified frame rate.
+  bool external_begin_frame_enabled;
+  int begin_frame_rate;
+};
+
+}  // namespace client
+
+#endif  // CEF_TESTS_CEFCLIENT_BROWSER_OSR_RENDERER_SETTINGS_H_
diff --git tests/cefclient/browser/osr_window_win.cc tests/cefclient/browser/osr_window_win.cc
index 288c964a..cd75f6c6 100644
--- tests/cefclient/browser/osr_window_win.cc
+++ tests/cefclient/browser/osr_window_win.cc
@@ -14,6 +14,8 @@
 #include "tests/cefclient/browser/osr_accessibility_helper.h"
 #include "tests/cefclient/browser/osr_accessibility_node.h"
 #include "tests/cefclient/browser/osr_ime_handler_win.h"
+#include "tests/cefclient/browser/osr_render_handler_win_d3d11.h"
+#include "tests/cefclient/browser/osr_render_handler_win_gl.h"
 #include "tests/cefclient/browser/resource.h"
 #include "tests/shared/browser/geometry_util.h"
 #include "tests/shared/browser/main_message_loop.h"
@@ -25,44 +27,14 @@ namespace {
 
 const wchar_t kWndClass[] = L"Client_OsrWindow";
 
-// Render at 30fps during rotation.
-const int kRenderDelay = 1000 / 30;
-
-// Helper that calls wglMakeCurrent.
-class ScopedGLContext {
- public:
-  ScopedGLContext(HDC hdc, HGLRC hglrc, bool swap_buffers)
-      : hdc_(hdc), swap_buffers_(swap_buffers) {
-    BOOL result = wglMakeCurrent(hdc, hglrc);
-    ALLOW_UNUSED_LOCAL(result);
-    DCHECK(result);
-  }
-  ~ScopedGLContext() {
-    BOOL result = wglMakeCurrent(NULL, NULL);
-    DCHECK(result);
-    if (swap_buffers_) {
-      result = SwapBuffers(hdc_);
-      DCHECK(result);
-    }
-  }
-
- private:
-  const HDC hdc_;
-  const bool swap_buffers_;
-};
-
 }  // namespace
 
 OsrWindowWin::OsrWindowWin(Delegate* delegate,
-                           const OsrRenderer::Settings& settings)
+                           const OsrRendererSettings& settings)
     : delegate_(delegate),
-      renderer_(settings),
+      settings_(settings),
       hwnd_(NULL),
-      hdc_(NULL),
-      hrc_(NULL),
       device_scale_factor_(0),
-      painting_popup_(false),
-      render_task_pending_(false),
       hidden_(false),
       last_mouse_pos_(),
       current_mouse_pos_(),
@@ -80,7 +52,7 @@ OsrWindowWin::OsrWindowWin(Delegate* delegate,
 OsrWindowWin::~OsrWindowWin() {
   CEF_REQUIRE_UI_THREAD();
   // The native window should have already been destroyed.
-  DCHECK(!hwnd_);
+  DCHECK(!hwnd_ && !render_handler_.get());
 }
 
 void OsrWindowWin::CreateBrowser(HWND parent_hwnd,
@@ -103,6 +75,10 @@ void OsrWindowWin::CreateBrowser(HWND parent_hwnd,
   CefWindowInfo window_info;
   window_info.SetAsWindowless(hwnd_);
 
+  window_info.shared_texture_enabled = settings_.shared_texture_enabled;
+  window_info.external_begin_frame_enabled =
+      settings_.external_begin_frame_enabled;
+
   // Create the browser asynchronously.
   CefBrowserHost::CreateBrowser(window_info, handler, startup_url, settings,
                                 request_context);
@@ -127,6 +103,10 @@ void OsrWindowWin::ShowPopup(HWND parent_hwnd,
                      y + static_cast<int>(height)};
   Create(parent_hwnd, rect);
 
+  // Create the render handler.
+  EnsureRenderHandler();
+  render_handler_->SetBrowser(browser_);
+
   // Send resize notification so the compositor is assigned the correct
   // viewport size and begins rendering.
   browser_->GetHost()->WasResized();
@@ -226,7 +206,7 @@ void OsrWindowWin::SetDeviceScaleFactor(float device_scale_factor) {
 
 void OsrWindowWin::Create(HWND parent_hwnd, const RECT& rect) {
   CEF_REQUIRE_UI_THREAD();
-  DCHECK(!hwnd_ && !hdc_ && !hrc_);
+  DCHECK(!hwnd_ && !render_handler_.get());
   DCHECK(parent_hwnd);
   DCHECK(!::IsRectEmpty(&rect));
 
@@ -278,7 +258,7 @@ void OsrWindowWin::Destroy() {
   drop_target_ = NULL;
 #endif
 
-  DisableGL();
+  render_handler_.reset();
 
   // Destroy the native window.
   ::DestroyWindow(hwnd_);
@@ -286,82 +266,6 @@ void OsrWindowWin::Destroy() {
   hwnd_ = NULL;
 }
 
-void OsrWindowWin::EnableGL() {
-  CEF_REQUIRE_UI_THREAD();
-
-  PIXELFORMATDESCRIPTOR pfd;
-  int format;
-
-  // Get the device context.
-  hdc_ = GetDC(hwnd_);
-
-  // Set the pixel format for the DC.
-  ZeroMemory(&pfd, sizeof(pfd));
-  pfd.nSize = sizeof(pfd);
-  pfd.nVersion = 1;
-  pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
-  pfd.iPixelType = PFD_TYPE_RGBA;
-  pfd.cColorBits = 24;
-  pfd.cDepthBits = 16;
-  pfd.iLayerType = PFD_MAIN_PLANE;
-  format = ChoosePixelFormat(hdc_, &pfd);
-  SetPixelFormat(hdc_, format, &pfd);
-
-  // Create and enable the render context.
-  hrc_ = wglCreateContext(hdc_);
-
-  ScopedGLContext scoped_gl_context(hdc_, hrc_, false);
-  renderer_.Initialize();
-}
-
-void OsrWindowWin::DisableGL() {
-  CEF_REQUIRE_UI_THREAD();
-
-  if (!hdc_)
-    return;
-
-  {
-    ScopedGLContext scoped_gl_context(hdc_, hrc_, false);
-    renderer_.Cleanup();
-  }
-
-  if (IsWindow(hwnd_)) {
-    // wglDeleteContext will make the context not current before deleting it.
-    BOOL result = wglDeleteContext(hrc_);
-    ALLOW_UNUSED_LOCAL(result);
-    DCHECK(result);
-    ReleaseDC(hwnd_, hdc_);
-  }
-
-  hdc_ = NULL;
-  hrc_ = NULL;
-}
-
-void OsrWindowWin::Invalidate() {
-  CEF_REQUIRE_UI_THREAD();
-
-  // Don't post another task if the previous task is still pending.
-  if (render_task_pending_)
-    return;
-  render_task_pending_ = true;
-
-  CefPostDelayedTask(TID_UI, base::Bind(&OsrWindowWin::Render, this),
-                     kRenderDelay);
-}
-
-void OsrWindowWin::Render() {
-  CEF_REQUIRE_UI_THREAD();
-
-  if (render_task_pending_)
-    render_task_pending_ = false;
-
-  if (!hdc_)
-    EnableGL();
-
-  ScopedGLContext scoped_gl_context(hdc_, hrc_, true);
-  renderer_.Render();
-}
-
 void OsrWindowWin::NotifyNativeWindowCreated(HWND hwnd) {
   if (!CURRENTLY_ON_MAIN_THREAD()) {
     // Execute this method on the main thread.
@@ -647,8 +551,7 @@ void OsrWindowWin::OnMouseEvent(UINT message, WPARAM wParam, LPARAM lParam) {
       if (mouse_rotation_) {
         // End rotation effect.
         mouse_rotation_ = false;
-        renderer_.SetSpin(0, 0);
-        Invalidate();
+        render_handler_->SetSpin(0, 0);
       } else {
         int x = GET_X_LPARAM(lParam);
         int y = GET_Y_LPARAM(lParam);
@@ -680,11 +583,11 @@ void OsrWindowWin::OnMouseEvent(UINT message, WPARAM wParam, LPARAM lParam) {
         // Apply rotation effect.
         current_mouse_pos_.x = x;
         current_mouse_pos_.y = y;
-        renderer_.IncrementSpin(current_mouse_pos_.x - last_mouse_pos_.x,
-                                current_mouse_pos_.y - last_mouse_pos_.y);
+        render_handler_->IncrementSpin(
+            current_mouse_pos_.x - last_mouse_pos_.x,
+            current_mouse_pos_.y - last_mouse_pos_.y);
         last_mouse_pos_.x = current_mouse_pos_.x;
         last_mouse_pos_.y = current_mouse_pos_.y;
-        Invalidate();
       } else {
         if (!mouse_tracking_) {
           // Start tracking mouse leave. Required for the WM_MOUSELEAVE event to
@@ -819,21 +722,17 @@ bool OsrWindowWin::OnEraseBkgnd() {
 }
 
 bool OsrWindowWin::IsOverPopupWidget(int x, int y) const {
-  CEF_REQUIRE_UI_THREAD();
-  const CefRect& rc = renderer_.popup_rect();
-  int popup_right = rc.x + rc.width;
-  int popup_bottom = rc.y + rc.height;
-  return (x >= rc.x) && (x < popup_right) && (y >= rc.y) && (y < popup_bottom);
+  if (!render_handler_)
+    return false;
+  return render_handler_->IsOverPopupWidget(x, y);
 }
 
 int OsrWindowWin::GetPopupXOffset() const {
-  CEF_REQUIRE_UI_THREAD();
-  return renderer_.original_popup_rect().x - renderer_.popup_rect().x;
+  return render_handler_->GetPopupXOffset();
 }
 
 int OsrWindowWin::GetPopupYOffset() const {
-  CEF_REQUIRE_UI_THREAD();
-  return renderer_.original_popup_rect().y - renderer_.popup_rect().y;
+  return render_handler_->GetPopupYOffset();
 }
 
 void OsrWindowWin::ApplyPopupOffset(int& x, int& y) const {
@@ -848,6 +747,12 @@ void OsrWindowWin::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
   DCHECK(!browser_);
   browser_ = browser;
 
+  if (hwnd_) {
+    // The native window will already exist for non-popup browsers.
+    EnsureRenderHandler();
+    render_handler_->SetBrowser(browser);
+  }
+
   if (hwnd_) {
     // Show the browser window. Called asynchronously so that the browser has
     // time to create associated internal objects.
@@ -861,6 +766,7 @@ void OsrWindowWin::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
   static_cast<ClientHandlerOsr*>(browser_->GetHost()->GetClient().get())
       ->DetachOsrDelegate();
   browser_ = NULL;
+  render_handler_->SetBrowser(NULL);
   Destroy();
 }
 
@@ -923,20 +829,13 @@ bool OsrWindowWin::GetScreenInfo(CefRefPtr<CefBrowser> browser,
 }
 
 void OsrWindowWin::OnPopupShow(CefRefPtr<CefBrowser> browser, bool show) {
-  CEF_REQUIRE_UI_THREAD();
-
-  if (!show) {
-    renderer_.ClearPopupRects();
-    browser->GetHost()->Invalidate(PET_VIEW);
-  }
-  renderer_.OnPopupShow(browser, show);
+  render_handler_->OnPopupShow(browser, show);
 }
 
 void OsrWindowWin::OnPopupSize(CefRefPtr<CefBrowser> browser,
                                const CefRect& rect) {
-  CEF_REQUIRE_UI_THREAD();
-
-  renderer_.OnPopupSize(browser, LogicalToDevice(rect, device_scale_factor_));
+  render_handler_->OnPopupSize(browser,
+                               LogicalToDevice(rect, device_scale_factor_));
 }
 
 void OsrWindowWin::OnPaint(CefRefPtr<CefBrowser> browser,
@@ -945,23 +844,17 @@ void OsrWindowWin::OnPaint(CefRefPtr<CefBrowser> browser,
                            const void* buffer,
                            int width,
                            int height) {
-  CEF_REQUIRE_UI_THREAD();
+  EnsureRenderHandler();
+  render_handler_->OnPaint(browser, type, dirtyRects, buffer, width, height);
+}
 
-  if (painting_popup_) {
-    renderer_.OnPaint(browser, type, dirtyRects, buffer, width, height);
-    return;
-  }
-  if (!hdc_)
-    EnableGL();
-
-  ScopedGLContext scoped_gl_context(hdc_, hrc_, true);
-  renderer_.OnPaint(browser, type, dirtyRects, buffer, width, height);
-  if (type == PET_VIEW && !renderer_.popup_rect().IsEmpty()) {
-    painting_popup_ = true;
-    browser->GetHost()->Invalidate(PET_POPUP);
-    painting_popup_ = false;
-  }
-  renderer_.Render();
+void OsrWindowWin::OnAcceleratedPaint(
+    CefRefPtr<CefBrowser> browser,
+    CefRenderHandler::PaintElementType type,
+    const CefRenderHandler::RectList& dirtyRects,
+    void* share_handle) {
+  EnsureRenderHandler();
+  render_handler_->OnAcceleratedPaint(browser, type, dirtyRects, share_handle);
 }
 
 void OsrWindowWin::OnCursorChange(CefRefPtr<CefBrowser> browser,
@@ -1094,6 +987,31 @@ CefBrowserHost::DragOperationsMask OsrWindowWin::OnDrop(
   return current_drag_op_;
 }
 
+void OsrWindowWin::EnsureRenderHandler() {
+  CEF_REQUIRE_UI_THREAD();
+  if (!render_handler_) {
+    if (settings_.shared_texture_enabled) {
+      // Try to initialize D3D11 rendering.
+      auto render_handler = new OsrRenderHandlerWinD3D11(settings_, hwnd_);
+      if (render_handler->Initialize(browser_,
+                                     client_rect_.right - client_rect_.left,
+                                     client_rect_.bottom - client_rect_.top)) {
+        render_handler_.reset(render_handler);
+      } else {
+        LOG(ERROR) << "Failed to initialize D3D11 rendering.";
+        delete render_handler;
+      }
+    }
+
+    // Fall back to GL rendering.
+    if (!render_handler_) {
+      auto render_handler = new OsrRenderHandlerWinGL(settings_, hwnd_);
+      render_handler->Initialize(browser_);
+      render_handler_.reset(render_handler);
+    }
+  }
+}
+
 #endif  // defined(CEF_USE_ATL)
 
 }  // namespace client
diff --git tests/cefclient/browser/osr_window_win.h tests/cefclient/browser/osr_window_win.h
index 900af2d8..effa97e6 100644
--- tests/cefclient/browser/osr_window_win.h
+++ tests/cefclient/browser/osr_window_win.h
@@ -13,7 +13,8 @@
 #include "tests/cefclient/browser/client_handler_osr.h"
 #include "tests/cefclient/browser/osr_accessibility_node.h"
 #include "tests/cefclient/browser/osr_dragdrop_win.h"
-#include "tests/cefclient/browser/osr_renderer.h"
+#include "tests/cefclient/browser/osr_render_handler_win.h"
+#include "tests/cefclient/browser/osr_renderer_settings.h"
 
 namespace client {
 
@@ -27,7 +28,7 @@ class OsrWindowWin
     : public base::RefCountedThreadSafe<OsrWindowWin, CefDeleteOnUIThread>,
       public ClientHandlerOsr::OsrDelegate
 #if defined(CEF_USE_ATL)
-      ,
+    ,
       public OsrDragEvents
 #endif
 {
@@ -44,7 +45,7 @@ class OsrWindowWin
   };
 
   // |delegate| must outlive this object.
-  OsrWindowWin(Delegate* delegate, const OsrRenderer::Settings& settings);
+  OsrWindowWin(Delegate* delegate, const OsrRendererSettings& settings);
 
   // Create a new browser and native window.
   void CreateBrowser(HWND parent_hwnd,
@@ -63,6 +64,8 @@ class OsrWindowWin
   void SetFocus();
   void SetDeviceScaleFactor(float device_scale_factor);
 
+  const OsrRendererSettings& settings() const { return settings_; }
+
  private:
   // Only allow deletion via scoped_refptr.
   friend struct CefDeleteOnThread<TID_UI>;
@@ -74,14 +77,6 @@ class OsrWindowWin
   void Create(HWND parent_hwnd, const RECT& rect);
   void Destroy();
 
-  // Manage GL context lifespan.
-  void EnableGL();
-  void DisableGL();
-
-  // Redraw what is currently in the texture.
-  void Invalidate();
-  void Render();
-
   void NotifyNativeWindowCreated(HWND hwnd);
 
   static void RegisterOsrClass(HINSTANCE hInstance, HBRUSH background_brush);
@@ -130,6 +125,10 @@ class OsrWindowWin
                const void* buffer,
                int width,
                int height) OVERRIDE;
+  void OnAcceleratedPaint(CefRefPtr<CefBrowser> browser,
+                          CefRenderHandler::PaintElementType type,
+                          const CefRenderHandler::RectList& dirtyRects,
+                          void* share_handle) OVERRIDE;
   void OnCursorChange(CefRefPtr<CefBrowser> browser,
                       CefCursorHandle cursor,
                       CefRenderHandler::CursorType type,
@@ -163,14 +162,14 @@ class OsrWindowWin
       CefBrowserHost::DragOperationsMask effect) OVERRIDE;
 #endif  // defined(CEF_USE_ATL)
 
+  void EnsureRenderHandler();
+
   // Only accessed on the main thread.
   Delegate* delegate_;
 
-  // The below members are only accessed on the UI thread.
-  OsrRenderer renderer_;
+  const OsrRendererSettings settings_;
   HWND hwnd_;
-  HDC hdc_;
-  HGLRC hrc_;
+  std::unique_ptr<OsrRenderHandlerWin> render_handler_;
 
   // Class that encapsulates IMM32 APIs and controls IMEs attached to a window.
   scoped_ptr<OsrImeHandlerWin> ime_handler_;
@@ -190,8 +189,6 @@ class OsrWindowWin
   IAccessible* accessibility_root_;
 #endif
 
-  bool painting_popup_;
-  bool render_task_pending_;
   bool hidden_;
 
   // Mouse state tracking.
diff --git tests/cefclient/browser/root_window_gtk.cc tests/cefclient/browser/root_window_gtk.cc
index 480dd096..c8c29d9f 100644
--- tests/cefclient/browser/root_window_gtk.cc
+++ tests/cefclient/browser/root_window_gtk.cc
@@ -231,7 +231,7 @@ bool RootWindowGtk::WithExtension() const {
 
 void RootWindowGtk::CreateBrowserWindow(const std::string& startup_url) {
   if (with_osr_) {
-    OsrRenderer::Settings settings = {};
+    OsrRendererSettings settings = {};
     MainContext::Get()->PopulateOsrSettings(&settings);
     browser_window_.reset(new BrowserWindowOsrGtk(this, startup_url, settings));
   } else {
diff --git tests/cefclient/browser/root_window_mac.mm tests/cefclient/browser/root_window_mac.mm
index 429f1303..87235014 100644
--- tests/cefclient/browser/root_window_mac.mm
+++ tests/cefclient/browser/root_window_mac.mm
@@ -445,7 +445,7 @@ void RootWindowMac::WindowDestroyed() {
 
 void RootWindowMac::CreateBrowserWindow(const std::string& startup_url) {
   if (with_osr_) {
-    OsrRenderer::Settings settings = {};
+    OsrRendererSettings settings = {};
     MainContext::Get()->PopulateOsrSettings(&settings);
     browser_window_.reset(new BrowserWindowOsrMac(this, startup_url, settings));
   } else {
diff --git tests/cefclient/browser/root_window_win.cc tests/cefclient/browser/root_window_win.cc
index 64e58b9c..b8c0461e 100644
--- tests/cefclient/browser/root_window_win.cc
+++ tests/cefclient/browser/root_window_win.cc
@@ -301,7 +301,7 @@ bool RootWindowWin::WithExtension() const {
 
 void RootWindowWin::CreateBrowserWindow(const std::string& startup_url) {
   if (with_osr_) {
-    OsrRenderer::Settings settings = {};
+    OsrRendererSettings settings = {};
     MainContext::Get()->PopulateOsrSettings(&settings);
     browser_window_.reset(new BrowserWindowOsrWin(this, startup_url, settings));
   } else {
diff --git tests/shared/browser/client_app_browser.cc tests/shared/browser/client_app_browser.cc
index fe2f2cae..038d3e29 100644
--- tests/shared/browser/client_app_browser.cc
+++ tests/shared/browser/client_app_browser.cc
@@ -21,15 +21,8 @@ void ClientAppBrowser::OnBeforeCommandLineProcessing(
   // Pass additional command-line flags to the browser process.
   if (process_type.empty()) {
     // Pass additional command-line flags when off-screen rendering is enabled.
-    if (command_line->HasSwitch(switches::kOffScreenRenderingEnabled)) {
-      // If the PDF extension is enabled then cc Surfaces must be disabled for
-      // PDFs to render correctly.
-      // See https://bitbucket.org/chromiumembedded/cef/issues/1689 for details.
-      if (!command_line->HasSwitch("disable-extensions") &&
-          !command_line->HasSwitch("disable-pdf-extension")) {
-        command_line->AppendSwitch("disable-surfaces");
-      }
-
+    if (command_line->HasSwitch(switches::kOffScreenRenderingEnabled) &&
+        !command_line->HasSwitch(switches::kSharedTextureEnabled)) {
       // Use software rendering and compositing (disable GPU) for increased FPS
       // and decreased CPU usage. This will also disable WebGL so remove these
       // switches if you need that capability.
diff --git tests/shared/browser/util_win.cc tests/shared/browser/util_win.cc
index a4b7b992..0be060fc 100644
--- tests/shared/browser/util_win.cc
+++ tests/shared/browser/util_win.cc
@@ -8,6 +8,22 @@
 
 namespace client {
 
+namespace {
+
+LARGE_INTEGER qi_freq_ = {};
+
+}  // namespace
+
+uint64_t GetTimeNow() {
+  if (!qi_freq_.HighPart && !qi_freq_.LowPart) {
+    QueryPerformanceFrequency(&qi_freq_);
+  }
+  LARGE_INTEGER t = {};
+  QueryPerformanceCounter(&t);
+  return static_cast<uint64_t>((t.QuadPart / double(qi_freq_.QuadPart)) *
+                               1000000);
+}
+
 void SetUserDataPtr(HWND hWnd, void* ptr) {
   SetLastError(ERROR_SUCCESS);
   LONG_PTR result =
diff --git tests/shared/browser/util_win.h tests/shared/browser/util_win.h
index 83919e98..a46d77e2 100644
--- tests/shared/browser/util_win.h
+++ tests/shared/browser/util_win.h
@@ -13,6 +13,9 @@
 
 namespace client {
 
+// Returns the current time in microseconds.
+uint64_t GetTimeNow();
+
 // Set the window's user data pointer.
 void SetUserDataPtr(HWND hWnd, void* ptr);
 
diff --git tests/shared/common/client_switches.cc tests/shared/common/client_switches.cc
index cfd68aa0..d5cf11d6 100644
--- tests/shared/common/client_switches.cc
+++ tests/shared/common/client_switches.cc
@@ -25,6 +25,8 @@ const char kOffScreenRenderingEnabled[] = "off-screen-rendering-enabled";
 const char kOffScreenFrameRate[] = "off-screen-frame-rate";
 const char kTransparentPaintingEnabled[] = "transparent-painting-enabled";
 const char kShowUpdateRect[] = "show-update-rect";
+const char kSharedTextureEnabled[] = "shared-texture-enabled";
+const char kExternalBeginFrameEnabled[] = "external-begin-frame-enabled";
 const char kMouseCursorChangeDisabled[] = "mouse-cursor-change-disabled";
 const char kRequestContextPerBrowser[] = "request-context-per-browser";
 const char kRequestContextSharedCache[] = "request-context-shared-cache";
diff --git tests/shared/common/client_switches.h tests/shared/common/client_switches.h
index 2bd34cb8..c6f6e8b2 100644
--- tests/shared/common/client_switches.h
+++ tests/shared/common/client_switches.h
@@ -19,6 +19,8 @@ extern const char kOffScreenRenderingEnabled[];
 extern const char kOffScreenFrameRate[];
 extern const char kTransparentPaintingEnabled[];
 extern const char kShowUpdateRect[];
+extern const char kSharedTextureEnabled[];
+extern const char kExternalBeginFrameEnabled[];
 extern const char kMouseCursorChangeDisabled[];
 extern const char kRequestContextPerBrowser[];
 extern const char kRequestContextSharedCache[];
