changeset 897:9c2f6d6b9f4a am-dev

Merge
author Alain Mazy <alain@mazy.be>
date Tue, 16 Jul 2019 12:37:29 +0200
parents 875bd6aca5e6 (current diff) 50cd372e2460 (diff)
children f9ae731fdc25
files Applications/Sdl/SdlOpenGLWindow.cpp Applications/Sdl/SdlOpenGLWindow.h Framework/Scene2D/OpenGLCompositor.cpp Framework/Scene2D/OpenGLCompositor.h Resources/CMake/OrthancStoneConfiguration.cmake Samples/Qt/BasicScene.cpp Samples/Qt/QStoneOpenGlWidget.cpp Samples/Qt/QStoneOpenGlWidget.h Samples/Sdl/BasicScene.cpp Samples/Sdl/CMakeLists.txt Samples/Sdl/FusionMprSdl.cpp Samples/Sdl/TrackerSampleApp.cpp Samples/Shared/SharedBasicScene.cpp Samples/WebAssembly/BasicScene.cpp Samples/WebAssembly/CMakeLists.txt Samples/WebAssembly/dev.h
diffstat 44 files changed, 1155 insertions(+), 511 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/SdlOpenGLContext.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -0,0 +1,122 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "SdlOpenGLContext.h"
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#if !defined(ORTHANC_ENABLE_GLEW)
+#  error Macro ORTHANC_ENABLE_GLEW must be defined
+#endif
+
+#if ORTHANC_ENABLE_GLEW == 1
+#  include <GL/glew.h>
+#endif
+
+#include <Core/OrthancException.h>
+
+namespace OrthancStone
+{
+  SdlOpenGLContext::SdlOpenGLContext(const char* title,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     bool allowDpiScaling) :
+    window_(title, width, height, true /* enable OpenGL */, allowDpiScaling)
+  {
+    context_ = SDL_GL_CreateContext(window_.GetObject());
+    
+    if (context_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                      "Cannot initialize OpenGL");
+    }
+
+#if ORTHANC_ENABLE_GLEW == 1
+    // The initialization function of glew (i.e. "glewInit()") can
+    // only be called once an OpenGL is setup.
+    // https://stackoverflow.com/a/45033669/881731
+    {
+      static boost::mutex  mutex_;
+      static bool          isGlewInitialized_ = false;
+  
+      boost::mutex::scoped_lock lock(mutex_);
+
+      if (!isGlewInitialized_)
+      {
+        LOG(INFO) << "Initializing glew";
+        
+        GLenum err = glewInit();
+        if (GLEW_OK != err)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                          "Cannot initialize glew");
+        }
+
+        isGlewInitialized_ = true;
+      }
+    }    
+#endif
+  }
+
+  
+  SdlOpenGLContext::~SdlOpenGLContext()
+  {
+    SDL_GL_DeleteContext(context_);
+  }
+
+
+  void SdlOpenGLContext::MakeCurrent()
+  {
+    if (SDL_GL_MakeCurrent(window_.GetObject(), context_) != 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
+                                      "Cannot set current OpenGL context");
+    }
+
+    // This makes our buffer swap syncronized with the monitor's vertical refresh
+    SDL_GL_SetSwapInterval(1);
+  }
+
+  
+  void SdlOpenGLContext::SwapBuffer()
+  {
+    // Swap our buffer to display the current contents of buffer on screen
+    SDL_GL_SwapWindow(window_.GetObject());
+  }
+
+  
+  unsigned int SdlOpenGLContext::GetCanvasWidth() const
+  {
+    int w = 0;
+    SDL_GL_GetDrawableSize(window_.GetObject(), &w, NULL);
+    return static_cast<unsigned int>(w);
+  }
+
+  
+  unsigned int SdlOpenGLContext::GetCanvasHeight() const
+  {
+    int h = 0;
+    SDL_GL_GetDrawableSize(window_.GetObject(), NULL, &h);
+    return static_cast<unsigned int>(h);
+  }
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/SdlOpenGLContext.h	Tue Jul 16 12:37:29 2019 +0200
@@ -0,0 +1,60 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_SDL == 1
+
+#include "../../Framework/OpenGL/IOpenGLContext.h"
+#include "SdlWindow.h"
+
+namespace OrthancStone
+{
+  class SdlOpenGLContext : public OpenGL::IOpenGLContext
+  {
+  private:
+    SdlWindow      window_;
+    SDL_GLContext  context_;
+
+  public:
+    SdlOpenGLContext(const char* title,
+                     unsigned int width,
+                     unsigned int height,
+                     bool allowDpiScaling = true);
+
+    ~SdlOpenGLContext();
+
+    SdlWindow& GetWindow()
+    {
+      return window_;
+    }
+
+    virtual void MakeCurrent();
+
+    virtual void SwapBuffer();
+
+    virtual unsigned int GetCanvasWidth() const;
+
+    virtual unsigned int GetCanvasHeight() const;
+  };
+}
+
+#endif
--- a/Applications/Sdl/SdlOpenGLWindow.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#include "SdlOpenGLWindow.h"
-
-#if ORTHANC_ENABLE_SDL == 1
-
-#if !defined(ORTHANC_ENABLE_GLEW)
-#  error Macro ORTHANC_ENABLE_GLEW must be defined
-#endif
-
-#if ORTHANC_ENABLE_GLEW == 1
-#  include <GL/glew.h>
-#endif
-
-#include <Core/OrthancException.h>
-
-namespace OrthancStone
-{
-  SdlOpenGLWindow::SdlOpenGLWindow(const char* title,
-                                   unsigned int width,
-                                   unsigned int height,
-                                   bool allowDpiScaling) :
-    window_(title, width, height, true /* enable OpenGL */, allowDpiScaling)
-  {
-    context_ = SDL_GL_CreateContext(window_.GetObject());
-    
-    if (context_ == NULL)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
-                                      "Cannot initialize OpenGL");
-    }
-
-#if ORTHANC_ENABLE_GLEW == 1
-    // The initialization function of glew (i.e. "glewInit()") can
-    // only be called once an OpenGL is setup.
-    // https://stackoverflow.com/a/45033669/881731
-    {
-      static boost::mutex  mutex_;
-      static bool          isGlewInitialized_ = false;
-  
-      boost::mutex::scoped_lock lock(mutex_);
-
-      if (!isGlewInitialized_)
-      {
-        LOG(INFO) << "Initializing glew";
-        
-        GLenum err = glewInit();
-        if (GLEW_OK != err)
-        {
-          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
-                                          "Cannot initialize glew");
-        }
-
-        isGlewInitialized_ = true;
-      }
-    }    
-#endif
-  }
-
-  
-  SdlOpenGLWindow::~SdlOpenGLWindow()
-  {
-    SDL_GL_DeleteContext(context_);
-  }
-
-
-  void SdlOpenGLWindow::MakeCurrent()
-  {
-    if (SDL_GL_MakeCurrent(window_.GetObject(), context_) != 0)
-    {
-      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
-                                      "Cannot set current OpenGL context");
-    }
-
-    // This makes our buffer swap syncronized with the monitor's vertical refresh
-    SDL_GL_SetSwapInterval(1);
-  }
-
-  
-  void SdlOpenGLWindow::SwapBuffer()
-  {
-    // Swap our buffer to display the current contents of buffer on screen
-    SDL_GL_SwapWindow(window_.GetObject());
-  }
-
-  
-  unsigned int SdlOpenGLWindow::GetCanvasWidth() const
-  {
-    int w = 0;
-    SDL_GL_GetDrawableSize(window_.GetObject(), &w, NULL);
-    return static_cast<unsigned int>(w);
-  }
-
-  
-  unsigned int SdlOpenGLWindow::GetCanvasHeight() const
-  {
-    int h = 0;
-    SDL_GL_GetDrawableSize(window_.GetObject(), NULL, &h);
-    return static_cast<unsigned int>(h);
-  }
-}
-
-#endif
--- a/Applications/Sdl/SdlOpenGLWindow.h	Fri Jul 12 14:57:48 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/**
- * Stone of Orthanc
- * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
- * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
- *
- * This program is free software: you can redistribute it and/or
- * modify it under the terms of the GNU Affero General Public License
- * as published by the Free Software Foundation, either version 3 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Affero General Public License for more details.
- * 
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- **/
-
-
-#pragma once
-
-#if ORTHANC_ENABLE_SDL == 1
-
-#include "../../Framework/OpenGL/IOpenGLContext.h"
-#include "SdlWindow.h"
-
-namespace OrthancStone
-{
-  class SdlOpenGLWindow : public OpenGL::IOpenGLContext
-  {
-  private:
-    SdlWindow      window_;
-    SDL_GLContext  context_;
-
-  public:
-    SdlOpenGLWindow(const char* title,
-                    unsigned int width,
-                    unsigned int height,
-                    bool allowDpiScaling = true);
-
-    ~SdlOpenGLWindow();
-
-    SdlWindow& GetWindow()
-    {
-      return window_;
-    }
-
-    virtual void MakeCurrent();
-
-    virtual void SwapBuffer();
-
-    virtual unsigned int GetCanvasWidth() const;
-
-    virtual unsigned int GetCanvasHeight() const;
-  };
-}
-
-#endif
--- a/Framework/OpenGL/WebAssemblyOpenGLContext.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/OpenGL/WebAssemblyOpenGLContext.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -21,8 +21,6 @@
 
 #include "WebAssemblyOpenGLContext.h"
 
-#if ORTHANC_ENABLE_WASM == 1
-
 #include <Core/OrthancException.h>
 
 #include <emscripten/html5.h>
@@ -175,5 +173,3 @@
     }
   }
 }
-
-#endif
--- a/Framework/OpenGL/WebAssemblyOpenGLContext.h	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/OpenGL/WebAssemblyOpenGLContext.h	Tue Jul 16 12:37:29 2019 +0200
@@ -25,7 +25,17 @@
 #  error Macro ORTHANC_ENABLE_WASM must be defined
 #endif
 
-#if ORTHANC_ENABLE_WASM == 1
+#if ORTHANC_ENABLE_WASM != 1
+#  error This file can only be used if targeting WebAssembly
+#endif
+
+#if !defined(ORTHANC_ENABLE_OPENGL)
+#  error The macro ORTHANC_ENABLE_OPENGL must be defined
+#endif
+
+#if ORTHANC_ENABLE_OPENGL != 1
+#  error Support for OpenGL is disabled
+#endif
 
 #include "IOpenGLContext.h"
 
@@ -58,5 +68,3 @@
     };
   }
 }
-
-#endif
--- a/Framework/Scene2D/OpenGLCompositor.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2D/OpenGLCompositor.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -134,7 +134,6 @@
     canvasWidth_(0),
     canvasHeight_(0)
   {
-    UpdateSize();
   }
 
   
@@ -148,20 +147,14 @@
   }
 
   
-  void OpenGLCompositor::UpdateSize()
-  {
-    canvasWidth_ = context_.GetCanvasWidth();
-    canvasHeight_ = context_.GetCanvasHeight();
-
-    context_.MakeCurrent();
-    glViewport(0, 0, canvasWidth_, canvasHeight_);
-  }
-
-  
   void OpenGLCompositor::Refresh()
   {
     context_.MakeCurrent();
 
+    canvasWidth_ = context_.GetCanvasWidth();
+    canvasHeight_ = context_.GetCanvasHeight();
+
+    glViewport(0, 0, canvasWidth_, canvasHeight_);
     glClearColor(0, 0, 0, 1);
     glClear(GL_COLOR_BUFFER_BIT);
 
@@ -174,6 +167,8 @@
   void OpenGLCompositor::SetFont(size_t index,
                                  const GlyphBitmapAlphabet& dict)
   {
+    context_.MakeCurrent();
+      
     std::auto_ptr<Font> font(new Font(dict));
       
     Fonts::iterator found = fonts_.find(index);
--- a/Framework/Scene2D/OpenGLCompositor.h	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2D/OpenGLCompositor.h	Tue Jul 16 12:37:29 2019 +0200
@@ -29,9 +29,7 @@
 
 namespace OrthancStone
 {
-  class OpenGLCompositor :
-      public ICompositor,
-      private Internals::CompositorHelper::IRendererFactory
+  class OpenGLCompositor : private Internals::CompositorHelper::IRendererFactory
   {
   private:
     class Font;
@@ -58,9 +56,7 @@
 
     ~OpenGLCompositor();
 
-    void UpdateSize();
-
-    virtual void Refresh();
+    void Refresh();
 
     void SetFont(size_t index,
                  const GlyphBitmapAlphabet& dict);
@@ -72,12 +68,12 @@
                  Orthanc::Encoding codepage);
 #endif
 
-    virtual unsigned int GetCanvasWidth() const
+    unsigned int GetCanvasWidth() const
     {
       return canvasWidth_;
     }
 
-    virtual unsigned int GetCanvasHeight() const
+    unsigned int GetCanvasHeight() const
     {
       return canvasHeight_;
     }
--- a/Framework/Scene2DViewport/AngleMeasureTool.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -120,7 +120,7 @@
   AngleMeasureTool::AngleHighlightArea AngleMeasureTool::AngleHitTest(ScenePoint2D p) const
   {
     const double pixelToScene =
-      GetScene()->GetCanvasToSceneTransform().ComputeZoom();
+      GetController()->GetScene().GetCanvasToSceneTransform().ComputeZoom();
     const double SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD = pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD * pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD;
 
     {
@@ -165,7 +165,7 @@
   boost::shared_ptr<IFlexiblePointerTracker> AngleMeasureTool::CreateEditionTracker(const PointerEvent& e)
   {
     ScenePoint2D scenePos = e.GetMainPosition().Apply(
-      GetScene()->GetCanvasToSceneTransform());
+      GetController()->GetScene().GetCanvasToSceneTransform());
 
     if (!HitTest(scenePos))
       return boost::shared_ptr<IFlexiblePointerTracker>();
@@ -233,7 +233,7 @@
             {
               PolylineSceneLayer::Chain chain;
               //TODO: take DPI into account
-              AddSquare(chain, GetScene(), side1End_, 
+              AddSquare(chain, GetController()->GetScene(), side1End_, 
                 GetController()->GetHandleSideLengthS());
               
               if (angleHighlightArea_ == AngleHighlightArea_Side1End)
@@ -245,7 +245,7 @@
             {
               PolylineSceneLayer::Chain chain;
               //TODO: take DPI into account
-              AddSquare(chain, GetScene(), side2End_, 
+              AddSquare(chain, GetController()->GetScene(), side2End_, 
                 GetController()->GetHandleSideLengthS());
 
               if (angleHighlightArea_ == AngleHighlightArea_Side2End)
@@ -294,7 +294,7 @@
           sprintf(buf, "%0.02f\xc2\xb0", angleDeg);
 
           SetTextLayerOutlineProperties(
-            GetScene(), layerHolder_, buf, ScenePoint2D(pointX, pointY));
+            GetController()->GetScene(), layerHolder_, buf, ScenePoint2D(pointX, pointY));
 
 #if 0
           // TODO:make it togglable
--- a/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -34,7 +34,7 @@
       new CreateAngleMeasureCommand(
         broker,
         controllerW,
-        e.GetMainPosition().Apply(GetScene()->GetCanvasToSceneTransform())));
+        e.GetMainPosition().Apply(GetScene().GetCanvasToSceneTransform())));
   }
 
   CreateAngleMeasureTracker::~CreateAngleMeasureTracker()
@@ -43,8 +43,6 @@
 
   void CreateAngleMeasureTracker::PointerMove(const PointerEvent& event)
   {
-    assert(GetScene());
-
     if (!alive_)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
@@ -53,7 +51,7 @@
     }
 
     ScenePoint2D scenePos = event.GetMainPosition().Apply(
-      GetScene()->GetCanvasToSceneTransform());
+      GetScene().GetCanvasToSceneTransform());
 
     switch (state_)
     {
--- a/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -33,7 +33,7 @@
       new CreateLineMeasureCommand(
         broker,
         controllerW,
-        e.GetMainPosition().Apply(GetScene()->GetCanvasToSceneTransform())));
+        e.GetMainPosition().Apply(GetScene().GetCanvasToSceneTransform())));
   }
 
   CreateLineMeasureTracker::~CreateLineMeasureTracker()
@@ -43,8 +43,6 @@
 
   void CreateLineMeasureTracker::PointerMove(const PointerEvent& event)
   {
-    assert(GetScene());
-    
     if (!alive_)
     {
       throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
@@ -53,7 +51,7 @@
     }
 
     ScenePoint2D scenePos = event.GetMainPosition().Apply(
-      GetScene()->GetCanvasToSceneTransform());
+      GetScene().GetCanvasToSceneTransform());
 
     //LOG(TRACE) << "scenePos.GetX() = " << scenePos.GetX() << "     " <<
     //  "scenePos.GetY() = " << scenePos.GetY();
--- a/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -30,7 +30,7 @@
     : EditMeasureTracker(controllerW, e)
   {
     ScenePoint2D scenePos = e.GetMainPosition().Apply(
-      GetScene()->GetCanvasToSceneTransform());
+      GetScene().GetCanvasToSceneTransform());
 
     modifiedZone_ = measureTool->AngleHitTest(scenePos);
 
@@ -45,7 +45,7 @@
   void EditAngleMeasureTracker::PointerMove(const PointerEvent& e)
   {
     ScenePoint2D scenePos = e.GetMainPosition().Apply(
-      GetScene()->GetCanvasToSceneTransform());
+      GetScene().GetCanvasToSceneTransform());
 
     ScenePoint2D delta = scenePos - GetOriginalClickPosition();
 
--- a/Framework/Scene2DViewport/EditLineMeasureTracker.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/EditLineMeasureTracker.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -21,16 +21,16 @@
 #include "EditLineMeasureTracker.h"
 
 namespace OrthancStone
-{
+{
   EditLineMeasureTracker::EditLineMeasureTracker(
     boost::shared_ptr<LineMeasureTool>  measureTool,
     MessageBroker& broker,
     boost::weak_ptr<ViewportController> controllerW,
-    const PointerEvent& e) 
+    const PointerEvent& e) 
     : EditMeasureTracker(controllerW, e)
-  {
+  {
     ScenePoint2D scenePos = e.GetMainPosition().Apply(
-      GetScene()->GetCanvasToSceneTransform());
+      GetScene().GetCanvasToSceneTransform());
 
     modifiedZone_ = measureTool->LineHitTest(scenePos);
 
@@ -39,7 +39,7 @@
         measureTool,
         broker,
         controllerW));
-  }
+  }
 
   EditLineMeasureTracker::~EditLineMeasureTracker()
   {
@@ -49,7 +49,7 @@
   void EditLineMeasureTracker::PointerMove(const PointerEvent& e)
   {
     ScenePoint2D scenePos = e.GetMainPosition().Apply(
-      GetScene()->GetCanvasToSceneTransform());
+      GetScene().GetCanvasToSceneTransform());
 
     ScenePoint2D delta = scenePos - GetOriginalClickPosition();
 
@@ -104,4 +104,4 @@
     return ret;
   }
 
-}
\ No newline at end of file
+}
--- a/Framework/Scene2DViewport/LayerHolder.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/LayerHolder.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -43,18 +43,18 @@
   {
     assert(baseLayerIndex_ == -1);
 
-    baseLayerIndex_ = GetScene()->GetMaxDepth() + 100;
+    baseLayerIndex_ = GetScene().GetMaxDepth() + 100;
 
     for (int i = 0; i < polylineLayerCount_; ++i)
     {
       std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer());
-      GetScene()->SetLayer(baseLayerIndex_ + i, layer.release());
+      GetScene().SetLayer(baseLayerIndex_ + i, layer.release());
     }
 
     for (int i = 0; i < textLayerCount_; ++i)
     {
       std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer());
-      GetScene()->SetLayer(
+      GetScene().SetLayer(
         baseLayerIndex_ + polylineLayerCount_ + i,
         layer.release());
     }
@@ -72,7 +72,7 @@
     return (baseLayerIndex_ != -1);
   }
 
-  boost::shared_ptr<Scene2D> LayerHolder::GetScene()
+  Scene2D& LayerHolder::GetScene()
   {
     boost::shared_ptr<ViewportController> controller = controllerW_.lock();
     ORTHANC_ASSERT(controller.get() != 0, "Zombie attack!");
@@ -83,8 +83,8 @@
   {
     for (int i = 0; i < textLayerCount_ + polylineLayerCount_; ++i)
     {
-      ORTHANC_ASSERT(GetScene()->HasLayer(baseLayerIndex_ + i), "No layer");
-      GetScene()->DeleteLayer(baseLayerIndex_ + i);
+      ORTHANC_ASSERT(GetScene().HasLayer(baseLayerIndex_ + i), "No layer");
+      GetScene().DeleteLayer(baseLayerIndex_ + i);
     }
     baseLayerIndex_ = -1;
   }
@@ -93,9 +93,9 @@
   {
     using namespace Orthanc;
     ORTHANC_ASSERT(baseLayerIndex_ != -1);
-    ORTHANC_ASSERT(GetScene()->HasLayer(GetPolylineLayerIndex(index)));
+    ORTHANC_ASSERT(GetScene().HasLayer(GetPolylineLayerIndex(index)));
     ISceneLayer* layer =
-      &(GetScene()->GetLayer(GetPolylineLayerIndex(index)));
+      &(GetScene().GetLayer(GetPolylineLayerIndex(index)));
 
     PolylineSceneLayer* concreteLayer =
       dynamic_cast<PolylineSceneLayer*>(layer);
@@ -108,9 +108,9 @@
   {
     using namespace Orthanc;
     ORTHANC_ASSERT(baseLayerIndex_ != -1);
-    ORTHANC_ASSERT(GetScene()->HasLayer(GetTextLayerIndex(index)));
+    ORTHANC_ASSERT(GetScene().HasLayer(GetTextLayerIndex(index)));
     ISceneLayer* layer =
-      &(GetScene()->GetLayer(GetTextLayerIndex(index)));
+      &(GetScene().GetLayer(GetTextLayerIndex(index)));
 
     TextSceneLayer* concreteLayer =
       dynamic_cast<TextSceneLayer*>(layer);
--- a/Framework/Scene2DViewport/LayerHolder.h	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/LayerHolder.h	Tue Jul 16 12:37:29 2019 +0200
@@ -89,7 +89,7 @@
   private:
     int GetPolylineLayerIndex(int index = 0);
     int GetTextLayerIndex(int index = 0);
-    boost::shared_ptr<Scene2D> GetScene();
+    Scene2D& GetScene();
 
     int textLayerCount_;
     int polylineLayerCount_;
--- a/Framework/Scene2DViewport/LineMeasureTool.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -97,7 +97,7 @@
   LineMeasureTool::LineHighlightArea LineMeasureTool::LineHitTest(ScenePoint2D p) const
   {
     const double pixelToScene =
-      GetScene()->GetCanvasToSceneTransform().ComputeZoom();
+      GetController()->GetScene().GetCanvasToSceneTransform().ComputeZoom();
     const double SQUARED_HIT_TEST_MAX_DISTANCE_SCENE_COORD = pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD * pixelToScene * HIT_TEST_MAX_DISTANCE_CANVAS_COORD;
 
     const double sqDistanceFromStart = ScenePoint2D::SquaredDistancePtPt(p, start_);
@@ -123,7 +123,7 @@
   boost::shared_ptr<IFlexiblePointerTracker> LineMeasureTool::CreateEditionTracker(const PointerEvent& e)
   {
     ScenePoint2D scenePos = e.GetMainPosition().Apply(
-      GetScene()->GetCanvasToSceneTransform());
+      GetController()->GetScene().GetCanvasToSceneTransform());
 
     if (!HitTest(scenePos))
       return boost::shared_ptr<IFlexiblePointerTracker>();
@@ -196,7 +196,7 @@
               PolylineSceneLayer::Chain chain;
               
               //TODO: take DPI into account
-              AddSquare(chain, GetScene(), start_, 
+              AddSquare(chain, GetController()->GetScene(), start_, 
                 GetController()->GetHandleSideLengthS());
               
               if (lineHighlightArea_ == LineHighlightArea_Start)
@@ -209,7 +209,7 @@
               PolylineSceneLayer::Chain chain;
               
               //TODO: take DPI into account
-              AddSquare(chain, GetScene(), end_, 
+              AddSquare(chain, GetController()->GetScene(), end_, 
                 GetController()->GetHandleSideLengthS());
               
               if (lineHighlightArea_ == LineHighlightArea_End)
@@ -235,7 +235,7 @@
           double midY = 0.5 * (end_.GetY() + start_.GetY());
 
           SetTextLayerOutlineProperties(
-            GetScene(), layerHolder_, buf, ScenePoint2D(midX, midY));
+            GetController()->GetScene(), layerHolder_, buf, ScenePoint2D(midX, midY));
         }
       }
       else
--- a/Framework/Scene2DViewport/MeasureTool.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTool.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -76,16 +76,6 @@
 #endif
   }
 
-  boost::shared_ptr<Scene2D> MeasureTool::GetScene()
-  {
-    return GetController()->GetScene();
-  }
-
-  boost::shared_ptr<const Scene2D> MeasureTool::GetScene() const
-  {
-    return GetController()->GetScene();
-  }
-
   MeasureTool::MeasureTool(MessageBroker& broker,
     boost::weak_ptr<ViewportController> controllerW)
     : IObserver(broker)
--- a/Framework/Scene2DViewport/MeasureTool.h	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTool.h	Tue Jul 16 12:37:29 2019 +0200
@@ -125,9 +125,6 @@
     boost::shared_ptr<const ViewportController> GetController() const;
     boost::shared_ptr<ViewportController>      GetController();
 
-    boost::shared_ptr<const Scene2D>            GetScene() const;
-    boost::shared_ptr<Scene2D>                 GetScene();
-
     /**
     enabled_ is not accessible by subclasses because there is a state machine
     that we do not wanna mess with
--- a/Framework/Scene2DViewport/MeasureToolsToolbox.h	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.h	Tue Jul 16 12:37:29 2019 +0200
@@ -31,9 +31,9 @@
   square sides are parallel to the canvas boundaries.
   */
   void AddSquare(PolylineSceneLayer::Chain& chain,
-    boost::shared_ptr<const Scene2D>     scene,
-    const ScenePoint2D& centerS,
-    const double& sideLengthS);
+                 const Scene2D&     scene,
+                 const ScenePoint2D& centerS,
+                 const double& sideLengthS);
 
   /**
     Creates an arc centered on c that goes
@@ -180,10 +180,11 @@
   from layerIndex, up to (and not including) layerIndex+5.
   */
   void SetTextLayerOutlineProperties(
-    boost::shared_ptr<Scene2D> scene, boost::shared_ptr<LayerHolder> layerHolder,
-    const char* text, ScenePoint2D p);
+    Scene2D& scene,
+    boost::shared_ptr<LayerHolder> layerHolder,
+    const char* text,
+    ScenePoint2D p);
 
-
-  std::ostream& operator<<(std::ostream& os, const ScenePoint2D& p);
+  std::ostream& operator<<(std::ostream& os, const ScenePoint2D& p);
 }
 
--- a/Framework/Scene2DViewport/MeasureTrackers.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTrackers.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -53,7 +53,7 @@
       command_->Undo();
   }
 
-  boost::shared_ptr<Scene2D> CreateMeasureTracker::GetScene()
+  Scene2D& CreateMeasureTracker::GetScene()
   {
     return controllerW_.lock()->GetScene();
   }
@@ -64,10 +64,10 @@
     , commitResult_(true)
   {
     boost::shared_ptr<ViewportController> controller = controllerW.lock();
-    originalClickPosition_ = e.GetMainPosition().Apply(controller->GetScene()->GetCanvasToSceneTransform());
+    originalClickPosition_ = e.GetMainPosition().Apply(controller->GetScene().GetCanvasToSceneTransform());
   }
 
-  boost::shared_ptr<Scene2D> EditMeasureTracker::GetScene()
+  Scene2D& EditMeasureTracker::GetScene()
   {
     return controllerW_.lock()->GetScene();
   }
--- a/Framework/Scene2DViewport/MeasureTrackers.h	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTrackers.h	Tue Jul 16 12:37:29 2019 +0200
@@ -45,7 +45,7 @@
     boost::shared_ptr<CreateMeasureCommand>         command_;
     boost::weak_ptr<ViewportController>          controllerW_;
     bool                            alive_;
-    boost::shared_ptr<Scene2D>                      GetScene();
+    Scene2D&                      GetScene();
 
   private:
     bool                            commitResult_;
@@ -65,8 +65,8 @@
     boost::shared_ptr<EditMeasureCommand> command_;
     boost::weak_ptr<ViewportController>   controllerW_;
     bool                                  alive_;
-    boost::shared_ptr<Scene2D>            GetScene();
-
+    Scene2D&            GetScene();
+    
     ScenePoint2D                          GetOriginalClickPosition() const
     {
       return originalClickPosition_;
--- a/Framework/Scene2DViewport/ViewportController.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -29,12 +29,15 @@
 
 namespace OrthancStone
 {
-  ViewportController::ViewportController(boost::weak_ptr<UndoStack> undoStackW, MessageBroker& broker)
+  ViewportController::ViewportController(boost::weak_ptr<UndoStack> undoStackW,
+                                         MessageBroker& broker,
+                                         IViewport& viewport)
     : IObservable(broker)
     , undoStackW_(undoStackW)
     , canvasToSceneFactor_(0.0)
+    , viewport_(viewport)
+    , scene_(viewport.GetScene())
   {
-    scene_ = boost::make_shared<Scene2D>();
   }
 
   boost::shared_ptr<UndoStack> ViewportController::GetUndoStack()
@@ -72,16 +75,6 @@
     return GetUndoStack()->CanRedo();
   }
   
-  boost::shared_ptr<const Scene2D> ViewportController::GetScene() const
-  {
-    return scene_;
-  }
-
-  boost::shared_ptr<Scene2D> ViewportController::GetScene()
-  {
-    return scene_;
-  }
-
   bool ViewportController::HandlePointerEvent(PointerEvent e)
   {
     throw StoneException(ErrorCode_NotImplemented);
@@ -111,18 +104,18 @@
 
   const OrthancStone::AffineTransform2D& ViewportController::GetCanvasToSceneTransform() const
   {
-    return scene_->GetCanvasToSceneTransform();
+    return GetScene().GetCanvasToSceneTransform();
   }
 
   const OrthancStone::AffineTransform2D& ViewportController::GetSceneToCanvasTransform() const
   {
-    return scene_->GetSceneToCanvasTransform();
+    return GetScene().GetSceneToCanvasTransform();
   }
 
   void ViewportController::SetSceneToCanvasTransform(
     const AffineTransform2D& transform)
   {
-    scene_->SetSceneToCanvasTransform(transform);
+    viewport_.GetScene().SetSceneToCanvasTransform(transform);
     BroadcastMessage(SceneTransformChanged(*this));
     
     // update the canvas to scene factor
@@ -133,7 +126,7 @@
   void ViewportController::FitContent(
     unsigned int canvasWidth, unsigned int canvasHeight)
   {
-    scene_->FitContent(canvasWidth, canvasHeight);
+    viewport_.GetScene().FitContent(canvasWidth, canvasHeight);
     BroadcastMessage(SceneTransformChanged(*this));
   }
 
@@ -159,7 +152,7 @@
     if (canvasToSceneFactor_ == 0)
     {
       canvasToSceneFactor_ =
-        GetScene()->GetCanvasToSceneTransform().ComputeZoom();
+        GetScene().GetCanvasToSceneTransform().ComputeZoom();
     }
     return canvasToSceneFactor_;
   }
--- a/Framework/Scene2DViewport/ViewportController.h	Fri Jul 12 14:57:48 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.h	Tue Jul 16 12:37:29 2019 +0200
@@ -22,7 +22,7 @@
 
 #include "PredeclaredTypes.h"
 
-#include "../Scene2D/Scene2D.h"
+#include "../Viewport/IViewport.h"
 #include "../Scene2D/PointerEvent.h"
 #include "../Scene2DViewport/IFlexiblePointerTracker.h"
 
@@ -80,10 +80,9 @@
     ORTHANC_STONE_DEFINE_ORIGIN_MESSAGE(__FILE__, __LINE__, \
       SceneTransformChanged, ViewportController);
 
-    ViewportController(boost::weak_ptr<UndoStack> undoStackW, MessageBroker& broker);
-
-    boost::shared_ptr<const Scene2D> GetScene() const;
-    boost::shared_ptr<Scene2D>      GetScene();
+    ViewportController(boost::weak_ptr<UndoStack> undoStackW,
+                       MessageBroker& broker,
+                       IViewport& viewport);
 
     /** 
     This method is called by the GUI system and should update/delete the
@@ -170,6 +169,20 @@
     /** forwarded to the UndoStack */
     bool CanRedo() const;
 
+    IViewport& GetViewport()
+    {
+      return viewport_;
+    }
+
+    Scene2D& GetScene()
+    {
+      return viewport_.GetScene();
+    }
+
+    const Scene2D& GetScene() const
+    {
+      return scene_;
+    }
 
   private:
     double GetCanvasToSceneFactor() const;
@@ -180,10 +193,14 @@
     boost::shared_ptr<const UndoStack>           GetUndoStack() const;
 
     std::vector<boost::shared_ptr<MeasureTool> > measureTools_;
-    boost::shared_ptr<Scene2D>                   scene_;
     boost::shared_ptr<IFlexiblePointerTracker>   tracker_;
     
     // this is cached
     mutable double              canvasToSceneFactor_;
+
+
+    // Refactoring on 2019-07-10: Removing shared_ptr from scene
+    IViewport&      viewport_;
+    const Scene2D&  scene_;  // As long as the viewport exists, its associated scene too   
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/IViewport.h	Tue Jul 16 12:37:29 2019 +0200
@@ -0,0 +1,52 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "../Scene2D/Scene2D.h"
+#include "../Scene2D/ScenePoint2D.h"
+
+namespace OrthancStone
+{
+  /**
+   * Class that combines a Scene2D with a canvas where to draw the
+   * scene. A call to "Refresh()" will update the content of the
+   * canvas.
+   **/  
+  class IViewport : public boost::noncopyable
+  {
+  public:
+    virtual ~IViewport()
+    {
+    }
+
+    virtual Scene2D& GetScene() = 0;
+
+    virtual void Refresh() = 0;
+
+    virtual unsigned int GetCanvasWidth() const = 0;
+
+    virtual unsigned int GetCanvasHeight() const = 0;
+
+    virtual const std::string& GetCanvasIdentifier() const = 0;
+
+    virtual ScenePoint2D GetPixelCenterCoordinates(int x, int y) const = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/SdlViewport.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -0,0 +1,47 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "SdlViewport.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/make_shared.hpp>
+
+namespace OrthancStone
+{
+  SdlViewport::SdlViewport(const char* title,
+                           unsigned int width,
+                           unsigned int height) :
+    ViewportBase(title),
+    context_(title, width, height),
+    compositor_(context_, GetScene())
+  {
+  }
+
+  SdlViewport::SdlViewport(const char* title,
+                           unsigned int width,
+                           unsigned int height,
+                           boost::shared_ptr<Scene2D>& scene) :
+    ViewportBase(title, scene),
+    context_(title, width, height),
+    compositor_(context_, GetScene())
+  {
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/SdlViewport.h	Tue Jul 16 12:37:29 2019 +0200
@@ -0,0 +1,86 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#if !defined(ORTHANC_ENABLE_SDL)
+#  error Macro ORTHANC_ENABLE_SDL must be defined
+#endif
+
+#if ORTHANC_ENABLE_SDL != 1
+#  error SDL must be enabled to use this file
+#endif
+
+#if !defined(ORTHANC_ENABLE_OPENGL)
+#  error The macro ORTHANC_ENABLE_OPENGL must be defined
+#endif
+
+#if ORTHANC_ENABLE_OPENGL != 1
+#  error Support for OpenGL is disabled
+#endif
+
+#include "../../Applications/Sdl/SdlOpenGLContext.h"
+#include "../Scene2D/OpenGLCompositor.h"
+#include "ViewportBase.h"
+
+namespace OrthancStone
+{
+  class SdlViewport : public ViewportBase
+  {
+  private:
+    SdlOpenGLContext  context_;
+    OpenGLCompositor  compositor_;
+
+  public:
+    SdlViewport(const char* title,
+                unsigned int width,
+                unsigned int height);
+
+    SdlViewport(const char* title,
+                unsigned int width,
+                unsigned int height,
+                boost::shared_ptr<Scene2D>& scene);
+
+    virtual void Refresh()
+    {
+      compositor_.Refresh();
+    }
+
+    virtual unsigned int GetCanvasWidth() const
+    {
+      return compositor_.GetCanvasWidth();
+    }
+
+    virtual unsigned int GetCanvasHeight() const
+    {
+      return compositor_.GetCanvasHeight();
+    }
+
+    OpenGLCompositor& GetCompositor()
+    {
+      return compositor_;
+    }
+
+    SdlOpenGLContext& GetContext()
+    {
+      return context_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/ViewportBase.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -0,0 +1,54 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "ViewportBase.h"
+
+#include <Core/OrthancException.h>
+
+#include <boost/make_shared.hpp>
+
+namespace OrthancStone
+{
+  ViewportBase::ViewportBase(const std::string& identifier) :
+    identifier_(identifier),
+    scene_(boost::make_shared<Scene2D>())
+  {
+  }
+
+  
+  ViewportBase::ViewportBase(const std::string& identifier,
+                             boost::shared_ptr<Scene2D>& scene) :
+    identifier_(identifier),
+    scene_(scene)
+  {
+    if (scene.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
+    }
+  }
+  
+
+  ScenePoint2D ViewportBase::GetPixelCenterCoordinates(int x, int y) const
+  {
+    return ScenePoint2D(
+      static_cast<double>(x) + 0.5 - static_cast<double>(GetCanvasWidth()) / 2.0,
+      static_cast<double>(y) + 0.5 - static_cast<double>(GetCanvasHeight()) / 2.0);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/ViewportBase.h	Tue Jul 16 12:37:29 2019 +0200
@@ -0,0 +1,53 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "IViewport.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace OrthancStone
+{
+  class ViewportBase : public IViewport
+  {
+  private:
+    std::string                 identifier_;
+    boost::shared_ptr<Scene2D>  scene_;
+
+  public:
+    ViewportBase(const std::string& identifier);
+
+    ViewportBase(const std::string& identifier,
+                 boost::shared_ptr<Scene2D>& scene);
+
+    virtual Scene2D& GetScene()
+    {
+      return *scene_;
+    }
+
+    virtual const std::string& GetCanvasIdentifier() const
+    {
+      return identifier_;
+    }
+
+    virtual ScenePoint2D GetPixelCenterCoordinates(int x, int y) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/WebAssemblyViewport.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -0,0 +1,48 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "WebAssemblyViewport.h"
+
+namespace OrthancStone
+{
+  WebAssemblyViewport::WebAssemblyViewport(const std::string& canvas) :
+    ViewportBase(canvas),
+    context_(canvas),
+    compositor_(context_, GetScene())
+  {
+  }
+
+    
+  WebAssemblyViewport::WebAssemblyViewport(const std::string& canvas,
+                                           boost::shared_ptr<Scene2D>& scene) :
+    ViewportBase(canvas, scene),
+    context_(canvas),
+    compositor_(context_, GetScene())
+  {
+  }
+    
+
+  void WebAssemblyViewport::UpdateSize()
+  {
+    context_.UpdateSize();  // First read the size of the canvas
+    compositor_.Refresh();  // Then refresh the content of the canvas
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Framework/Viewport/WebAssemblyViewport.h	Tue Jul 16 12:37:29 2019 +0200
@@ -0,0 +1,65 @@
+/**
+ * Stone of Orthanc
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../OpenGL/WebAssemblyOpenGLContext.h"
+#include "../Scene2D/OpenGLCompositor.h"
+#include "ViewportBase.h"
+
+namespace OrthancStone
+{
+  class WebAssemblyViewport : public ViewportBase
+  {
+  private:
+    OpenGL::WebAssemblyOpenGLContext  context_;
+    OpenGLCompositor                  compositor_;
+
+  public:
+    WebAssemblyViewport(const std::string& canvas);
+    
+    WebAssemblyViewport(const std::string& canvas,
+                        boost::shared_ptr<Scene2D>& scene);
+    
+    virtual void Refresh()
+    {
+      compositor_.Refresh();
+    }
+
+    virtual unsigned int GetCanvasWidth() const
+    {
+      return context_.GetCanvasWidth();
+    }
+
+    virtual unsigned int GetCanvasHeight() const
+    {
+      return context_.GetCanvasHeight();
+    }
+
+    // This function must be called each time the browser window is resized
+    void UpdateSize();
+
+    OpenGLCompositor& GetCompositor()
+    {
+      return compositor_;
+    }
+  };
+}
--- a/Resources/CMake/OrthancStoneConfiguration.cmake	Fri Jul 12 14:57:48 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Tue Jul 16 12:37:29 2019 +0200
@@ -276,11 +276,17 @@
       list(APPEND APPLICATIONS_SOURCES
         ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlCairoSurface.cpp
         ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlEngine.cpp
-        ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOpenGLWindow.cpp
         ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOrthancSurface.cpp
         ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlStoneApplicationRunner.cpp
         ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlWindow.cpp
         )
+
+      if (ENABLE_OPENGL)
+        list(APPEND APPLICATIONS_SOURCES
+          ${ORTHANC_STONE_ROOT}/Applications/Sdl/SdlOpenGLContext.cpp
+          ${ORTHANC_STONE_ROOT}/Framework/Viewport/SdlViewport.cpp
+          )
+      endif()
     endif()
   endif()
 elseif (ENABLE_WASM)
@@ -518,6 +524,7 @@
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/ShearWarpProjectiveTransform.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/SlicesSorter.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Toolbox/UndoRedoStack.cpp
+  ${ORTHANC_STONE_ROOT}/Framework/Viewport/ViewportBase.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/DicomVolumeImage.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/DicomVolumeImageMPRSlicer.cpp
   ${ORTHANC_STONE_ROOT}/Framework/Volumes/DicomVolumeImageReslicer.cpp
@@ -590,6 +597,7 @@
   if (ENABLE_WASM)
     list(APPEND ORTHANC_STONE_SOURCES
       ${ORTHANC_STONE_ROOT}/Framework/OpenGL/WebAssemblyOpenGLContext.cpp
+      ${ORTHANC_STONE_ROOT}/Framework/Viewport/WebAssemblyViewport.cpp
       )
   endif()
 endif()
--- a/Samples/Qt/BasicScene.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/Qt/BasicScene.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -21,7 +21,7 @@
 #define GLEW_STATIC 1
 // From Stone
 #include "../../Framework/OpenGL/OpenGLIncludes.h"
-#include "../../Applications/Sdl/SdlOpenGLWindow.h"
+#include "../../Applications/Sdl/SdlWindow.h"
 #include "../../Framework/Scene2D/CairoCompositor.h"
 #include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
 #include "../../Framework/Scene2D/OpenGLCompositor.h"
@@ -94,7 +94,7 @@
     boost::shared_ptr<UndoStack> undoStack(new UndoStack);
     boost::shared_ptr<ViewportController> controller = boost::make_shared<ViewportController>(
       undoStack, boost::ref(broker));
-    PrepareScene(*(controller->GetScene()));
+    PrepareScene(controller->GetScene());
 
     boost::shared_ptr<OrthancStone::Scene2DInteractor> interactor(new BasicScene2DInteractor(controller));
     window.GetOpenGlWidget().SetInteractor(interactor);
--- a/Samples/Qt/QStoneOpenGlWidget.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/Qt/QStoneOpenGlWidget.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -33,11 +33,11 @@
     OrthancStone::GuiAdapterMouseEvent& guiEvent,
     PointerEvent& pointerEvent,
     const QMouseEvent& qtEvent,
-    const OrthancStone::OpenGLCompositor& compositor)
+    const IViewport& viewport)
 {
   guiEvent.targetX = qtEvent.x();
   guiEvent.targetY = qtEvent.y();
-  pointerEvent.AddPosition(compositor.GetPixelCenterCoordinates(guiEvent.targetX, guiEvent.targetY));
+  pointerEvent.AddPosition(viewport.GetPixelCenterCoordinates(guiEvent.targetX, guiEvent.targetY));
 
   switch (qtEvent.button())
   {
@@ -66,7 +66,7 @@
 {
   OrthancStone::GuiAdapterMouseEvent guiEvent;
   PointerEvent pointerEvent;
-  ConvertFromPlatform(guiEvent, pointerEvent, *qtEvent, *compositor_);
+  ConvertFromPlatform(guiEvent, pointerEvent, *qtEvent, *viewport_);
   guiEvent.type = guiEventType;
 
   if (sceneInteractor_.get() != NULL && compositor_.get() != NULL)
--- a/Samples/Qt/QStoneOpenGlWidget.h	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/Qt/QStoneOpenGlWidget.h	Tue Jul 16 12:37:29 2019 +0200
@@ -6,12 +6,14 @@
 #include <boost/shared_ptr.hpp>
 #include "../../Framework/OpenGL/IOpenGLContext.h"
 #include "../../Framework/Scene2D/OpenGLCompositor.h"
+#include "../../Framework/Viewport/IViewport.h"
 #include "../../Applications/Generic/Scene2DInteractor.h"
 
 namespace OrthancStone
 {
   class QStoneOpenGlWidget : public QOpenGLWidget, public OrthancStone::OpenGL::IOpenGLContext
   {
+    boost::shared_ptr<IViewport> viewport_;
     boost::shared_ptr<OrthancStone::OpenGLCompositor> compositor_;
     boost::shared_ptr<Scene2DInteractor> sceneInteractor_;
 
--- a/Samples/Sdl/BasicScene.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/Sdl/BasicScene.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -20,71 +20,243 @@
 
 
 // From Stone
-#include "../../Applications/Sdl/SdlOpenGLWindow.h"
+#include "../../Framework/Viewport/SdlViewport.h"
+#include "../../Framework/Scene2D/CairoCompositor.h"
+#include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
 #include "../../Framework/Scene2D/OpenGLCompositor.h"
+#include "../../Framework/Scene2D/PanSceneTracker.h"
+#include "../../Framework/Scene2D/RotateSceneTracker.h"
+#include "../../Framework/Scene2D/ZoomSceneTracker.h"
+#include "../../Framework/Scene2DViewport/ViewportController.h"
 #include "../../Framework/Scene2DViewport/UndoStack.h"
+
 #include "../../Framework/StoneInitialization.h"
 #include "../../Framework/Messages/MessageBroker.h"
 
 // From Orthanc framework
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
+#include <Core/Images/Image.h>
+#include <Core/Images/ImageProcessing.h>
+#include <Core/Images/PngWriter.h>
 
 #include <boost/make_shared.hpp>
-#include <boost/ref.hpp>
 
 #include <SDL.h>
 #include <stdio.h>
 
-
-#include "../Shared/SharedBasicScene.h"
-
-using namespace OrthancStone;
+static const unsigned int FONT_SIZE = 32;
+static const int LAYER_POSITION = 150;
 
-boost::shared_ptr<BasicScene2DInteractor> interactor;
 
-void HandleApplicationEvent(boost::shared_ptr<OrthancStone::ViewportController> controller,
-                            const OrthancStone::OpenGLCompositor& compositor,
-                            const SDL_Event& event)
+void PrepareScene(OrthancStone::Scene2D& scene)
 {
   using namespace OrthancStone;
-  Scene2D& scene(*controller->GetScene());
-  if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEMOTION)
+
+  // Texture of 2x2 size
+  {
+    Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false);
+    
+    uint8_t *p = reinterpret_cast<uint8_t*>(i.GetRow(0));
+    p[0] = 255;
+    p[1] = 0;
+    p[2] = 0;
+
+    p[3] = 0;
+    p[4] = 255;
+    p[5] = 0;
+
+    p = reinterpret_cast<uint8_t*>(i.GetRow(1));
+    p[0] = 0;
+    p[1] = 0;
+    p[2] = 255;
+
+    p[3] = 255;
+    p[4] = 0;
+    p[5] = 0;
+
+    scene.SetLayer(12, new ColorTextureSceneLayer(i));
+
+    std::auto_ptr<ColorTextureSceneLayer> l(new ColorTextureSceneLayer(i));
+    l->SetOrigin(-3, 2);
+    l->SetPixelSpacing(1.5, 1);
+    l->SetAngle(20.0 / 180.0 * M_PI);
+    scene.SetLayer(14, l.release());
+  }
+
+  // Texture of 1x1 size
+  {
+    Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false);
+    
+    uint8_t *p = reinterpret_cast<uint8_t*>(i.GetRow(0));
+    p[0] = 255;
+    p[1] = 0;
+    p[2] = 0;
+
+    std::auto_ptr<ColorTextureSceneLayer> l(new ColorTextureSceneLayer(i));
+    l->SetOrigin(-2, 1);
+    l->SetAngle(20.0 / 180.0 * M_PI);
+    scene.SetLayer(13, l.release());
+  }
+
+  // Some lines
   {
-    // TODO: this code is copy/pasted from GuiAdapter::Run() -> find the right place
+    std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer);
+
+    layer->SetThickness(10);
+
+    PolylineSceneLayer::Chain chain;
+    chain.push_back(ScenePoint2D(0 - 0.5, 0 - 0.5));
+    chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5));
+    chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5));
+    chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5));
+    layer->AddChain(chain, true, 255, 0, 0);
+
+    chain.clear();
+    chain.push_back(ScenePoint2D(-5, -5));
+    chain.push_back(ScenePoint2D(5, -5));
+    chain.push_back(ScenePoint2D(5, 5));
+    chain.push_back(ScenePoint2D(-5, 5));
+    layer->AddChain(chain, true, 0, 255, 0);
+
+    double dy = 1.01;
+    chain.clear();
+    chain.push_back(ScenePoint2D(-4, -4));
+    chain.push_back(ScenePoint2D(4, -4 + dy));
+    chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy));
+    chain.push_back(ScenePoint2D(4, 2));
+    layer->AddChain(chain, false, 0, 0, 255);
+
+    scene.SetLayer(50, layer.release());
+  }
+
+  // Some text
+  {
+    std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer);
+    layer->SetText("Hello");
+    scene.SetLayer(100, layer.release());
+  }
+}
+
+
+void TakeScreenshot(const std::string& target,
+                    const OrthancStone::Scene2D& scene,
+                    unsigned int canvasWidth,
+                    unsigned int canvasHeight)
+{
+  using namespace OrthancStone;
+  // Take a screenshot, then save it as PNG file
+  CairoCompositor compositor(scene, canvasWidth, canvasHeight);
+  compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, FONT_SIZE, Orthanc::Encoding_Latin1);
+  compositor.Refresh();
+
+  Orthanc::ImageAccessor canvas;
+  compositor.GetCanvas().GetReadOnlyAccessor(canvas);
+
+  Orthanc::Image png(Orthanc::PixelFormat_RGB24, canvas.GetWidth(), canvas.GetHeight(), false);
+  Orthanc::ImageProcessing::Convert(png, canvas);
+        
+  Orthanc::PngWriter writer;
+  writer.WriteToFile(target, png);
+}
+
+
+void HandleApplicationEvent(const SDL_Event& event,
+                            boost::shared_ptr<OrthancStone::ViewportController>& controller,
+                            boost::shared_ptr<OrthancStone::IFlexiblePointerTracker>& activeTracker)
+{
+  using namespace OrthancStone;
+
+  Scene2D& scene = controller->GetScene();
+  IViewport& viewport = controller->GetViewport();
+
+  if (event.type == SDL_MOUSEMOTION)
+  {
     int scancodeCount = 0;
     const uint8_t* keyboardState = SDL_GetKeyboardState(&scancodeCount);
-    bool ctrlPressed(false);
-    bool shiftPressed(false);
-    bool altPressed(false);
+
+    if (activeTracker.get() == NULL &&
+        SDL_SCANCODE_LCTRL < scancodeCount &&
+        keyboardState[SDL_SCANCODE_LCTRL])
+    {
+      // The "left-ctrl" key is down, while no tracker is present
 
-    if (SDL_SCANCODE_LCTRL < scancodeCount && keyboardState[SDL_SCANCODE_LCTRL])
-      ctrlPressed = true;
-    if (SDL_SCANCODE_RCTRL < scancodeCount && keyboardState[SDL_SCANCODE_RCTRL])
-      ctrlPressed = true;
-    if (SDL_SCANCODE_LSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_LSHIFT])
-      shiftPressed = true;
-    if (SDL_SCANCODE_RSHIFT < scancodeCount && keyboardState[SDL_SCANCODE_RSHIFT])
-      shiftPressed = true;
-    if (SDL_SCANCODE_LALT < scancodeCount && keyboardState[SDL_SCANCODE_LALT])
-      altPressed = true;
+      PointerEvent e;
+      e.AddPosition(viewport.GetPixelCenterCoordinates(event.button.x, event.button.y));
+
+      ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform());
+
+      char buf[64];
+      sprintf(buf, "(%0.02f,%0.02f)", p.GetX(), p.GetY());
 
-    GuiAdapterMouseEvent guiEvent;
-    ConvertFromPlatform(guiEvent, ctrlPressed, shiftPressed, altPressed, event);
-    PointerEvent pointerEvent;
-    pointerEvent.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
-
-    interactor->OnMouseEvent(guiEvent, pointerEvent);
-    return;
+      if (scene.HasLayer(LAYER_POSITION))
+      {
+        TextSceneLayer& layer =
+          dynamic_cast<TextSceneLayer&>(scene.GetLayer(LAYER_POSITION));
+        layer.SetText(buf);
+        layer.SetPosition(p.GetX(), p.GetY());
+      }
+      else
+      {
+        std::auto_ptr<TextSceneLayer> 
+          layer(new TextSceneLayer);
+        layer->SetColor(0, 255, 0);
+        layer->SetText(buf);
+        layer->SetBorder(20);
+        layer->SetAnchor(BitmapAnchor_BottomCenter);
+        layer->SetPosition(p.GetX(), p.GetY());
+        scene.SetLayer(LAYER_POSITION, layer.release());
+      }
+    }
+    else
+    {
+      scene.DeleteLayer(LAYER_POSITION);
+    }
   }
-  else if ((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) && event.key.repeat == 0  /* Ignore key bounce */)
+  else if (event.type == SDL_MOUSEBUTTONDOWN)
   {
-    GuiAdapterKeyboardEvent guiEvent;
-    ConvertFromPlatform(guiEvent, event);
+    PointerEvent e;
+    e.AddPosition(viewport.GetPixelCenterCoordinates(event.button.x, event.button.y));
+
+    switch (event.button.button)
+    {
+      case SDL_BUTTON_MIDDLE:
+        activeTracker = boost::make_shared<PanSceneTracker>(controller, e);
+        break;
+
+      case SDL_BUTTON_RIGHT:
+        activeTracker = boost::make_shared<ZoomSceneTracker>
+          (controller, e, viewport.GetCanvasHeight());
+        break;
+
+      case SDL_BUTTON_LEFT:
+        activeTracker = boost::make_shared<RotateSceneTracker>(controller, e);
+        break;
 
-    interactor->OnKeyboardEvent(guiEvent);
+      default:
+        break;
+    }
   }
-
+  else if (event.type == SDL_KEYDOWN &&
+           event.key.repeat == 0 /* Ignore key bounce */)
+  {
+    switch (event.key.keysym.sym)
+    {
+      case SDLK_s:
+        controller->FitContent(viewport.GetCanvasWidth(), 
+                               viewport.GetCanvasHeight());
+        break;
+              
+      case SDLK_c:
+        TakeScreenshot("screenshot.png", scene, 
+                       viewport.GetCanvasWidth(), 
+                       viewport.GetCanvasHeight());
+        break;
+              
+      default:
+        break;
+    }
+  }
 }
 
 
@@ -106,24 +278,24 @@
 }
 
 
-void Run(boost::shared_ptr<OrthancStone::ViewportController> controller)
+void Run(OrthancStone::MessageBroker& broker,
+         OrthancStone::SdlViewport& viewport)
 {
-  SdlOpenGLWindow window("Hello", 1024, 768);
-
-  controller->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight());
+  using namespace OrthancStone;
+  
+  boost::shared_ptr<ViewportController> controller(
+    new ViewportController(boost::make_shared<UndoStack>(), broker, viewport));
   
   glEnable(GL_DEBUG_OUTPUT);
   glDebugMessageCallback(OpenGLMessageCallback, 0);
 
-  boost::shared_ptr<OpenGLCompositor> compositor(new OpenGLCompositor(window, *controller->GetScene()));
-  compositor->SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT,
-                     BASIC_SCENE_FONT_SIZE, Orthanc::Encoding_Latin1);
-  interactor->SetCompositor(compositor);
+  boost::shared_ptr<IFlexiblePointerTracker> tracker;
 
+  bool firstShown = true;
   bool stop = false;
   while (!stop)
   {
-    compositor->Refresh();
+    viewport.Refresh();
 
     SDL_Event event;
     while (!stop &&
@@ -134,10 +306,49 @@
         stop = true;
         break;
       }
-      else if (event.type == SDL_WINDOWEVENT &&
-               event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
+      else if (event.type == SDL_MOUSEMOTION)
+      {
+        if (tracker)
+        {
+          PointerEvent e;
+          e.AddPosition(viewport.GetPixelCenterCoordinates(
+            event.button.x, event.button.y));
+          tracker->PointerMove(e);
+        }
+      }
+      else if (event.type == SDL_MOUSEBUTTONUP)
       {
-        compositor->UpdateSize();
+        if (tracker)
+        {
+          PointerEvent e;
+          e.AddPosition(viewport.GetPixelCenterCoordinates(
+            event.button.x, event.button.y));
+          tracker->PointerUp(e);
+          if(!tracker->IsAlive())
+            tracker.reset();
+        }
+      }
+      else if (event.type == SDL_WINDOWEVENT)
+      {
+        switch (event.window.event)
+        {
+          case SDL_WINDOWEVENT_SIZE_CHANGED:
+            tracker.reset();
+            break;
+
+          case SDL_WINDOWEVENT_SHOWN:
+            if (firstShown)
+            {
+              // Once the window is first shown, fit the content to its size
+              controller->FitContent(viewport.GetCanvasWidth(), viewport.GetCanvasHeight());
+              firstShown = false;
+            }
+            
+            break;
+
+          default:
+            break;
+        }
       }
       else if (event.type == SDL_KEYDOWN &&
                event.key.repeat == 0 /* Ignore key bounce */)
@@ -145,7 +356,7 @@
         switch (event.key.keysym.sym)
         {
           case SDLK_f:
-            window.GetWindow().ToggleMaximize();
+            viewport.GetContext().GetWindow().ToggleMaximize();
             break;
               
           case SDLK_q:
@@ -157,12 +368,11 @@
         }
       }
       
-      HandleApplicationEvent(controller, *compositor, event);
+      HandleApplicationEvent(event, controller, tracker);
     }
 
     SDL_Delay(1);
   }
-  interactor.reset();
 }
 
 
@@ -175,26 +385,26 @@
  **/
 int main(int argc, char* argv[])
 {
-  using namespace OrthancStone;
-  StoneInitialize();
+  OrthancStone::StoneInitialize();
   Orthanc::Logging::EnableInfoLevel(true);
 
   try
   {
-    MessageBroker broker;
-    boost::shared_ptr<UndoStack> undoStack(new UndoStack);
-    boost::shared_ptr<ViewportController> controller = boost::make_shared<ViewportController>(
-      undoStack, boost::ref(broker));
-    interactor.reset(new BasicScene2DInteractor(controller));
-    PrepareScene(controller);
-    Run(controller);
+    OrthancStone::SdlViewport viewport("Hello", 1024, 768);
+    PrepareScene(viewport.GetScene());
+
+    viewport.GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, 
+                                     FONT_SIZE, Orthanc::Encoding_Latin1);
+    
+    OrthancStone::MessageBroker broker;
+    Run(broker, viewport);
   }
   catch (Orthanc::OrthancException& e)
   {
     LOG(ERROR) << "EXCEPTION: " << e.What();
   }
 
-  StoneFinalize();
+  OrthancStone::StoneFinalize();
 
   return 0;
 }
--- a/Samples/Sdl/CMakeLists.txt	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/Sdl/CMakeLists.txt	Tue Jul 16 12:37:29 2019 +0200
@@ -66,8 +66,6 @@
 
 add_executable(BasicScene
   BasicScene.cpp
-  ${CMAKE_CURRENT_LIST_DIR}/../Shared/SharedBasicScene.h
-  ${CMAKE_CURRENT_LIST_DIR}/../Shared/SharedBasicScene.cpp
   )
 
 target_link_libraries(BasicScene OrthancStone)
--- a/Samples/Sdl/FusionMprSdl.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/Sdl/FusionMprSdl.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -20,7 +20,7 @@
 
 #include "FusionMprSdl.h"
 
-#include "../../Applications/Sdl/SdlOpenGLWindow.h"
+#include "../../Applications/Sdl/SdlOpenGLContext.h"
 
 #include "../../Framework/StoneInitialization.h"
 
@@ -587,7 +587,7 @@
   {
     // False means we do NOT let Windows treat this as a legacy application
     // that needs to be scaled
-    SdlOpenGLWindow window("Hello", 1024, 1024, false);
+    SdlOpenGLContext window("Hello", 1024, 1024, false);
 
     controller_->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight());
 
@@ -706,7 +706,6 @@
           event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
         {
           DisableTracker(); // was: tracker.reset(NULL);
-          compositor_->UpdateSize();
         }
         else if (event.type == SDL_KEYDOWN &&
           event.key.repeat == 0 /* Ignore key bounce */)
--- a/Samples/Sdl/TrackerSample.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/Sdl/TrackerSample.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -21,7 +21,7 @@
 #include "TrackerSampleApp.h"
 
  // From Stone
-#include "../../Applications/Sdl/SdlOpenGLWindow.h"
+#include "../../Applications/Sdl/SdlOpenGLContext.h"
 #include "../../Framework/Scene2D/CairoCompositor.h"
 #include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
 #include "../../Framework/Scene2D/OpenGLCompositor.h"
--- a/Samples/Sdl/TrackerSampleApp.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -20,7 +20,7 @@
 
 #include "TrackerSampleApp.h"
 
-#include "../../Applications/Sdl/SdlOpenGLWindow.h"
+#include "../../Applications/Sdl/SdlOpenGLContext.h"
 
 #include "../../Framework/Scene2D/CairoCompositor.h"
 #include "../../Framework/Scene2D/ColorTextureSceneLayer.h"
@@ -68,16 +68,6 @@
     return descs[i];
   }
 
-  boost::shared_ptr<Scene2D> TrackerSampleApp::GetScene()
-  {
-    return controller_->GetScene();
-  }
-
-  boost::shared_ptr<const Scene2D> TrackerSampleApp::GetScene() const
-  {
-    return controller_->GetScene();
-  }
-
   void TrackerSampleApp::SelectNextTool()
   {
     currentTool_ = static_cast<GuiTool>(currentTool_ + 1);
@@ -102,10 +92,10 @@
 	std::string msgS = msg.str();
 
     TextSceneLayer* layerP = NULL;
-    if (GetScene()->HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX))
+    if (controller_->GetScene().HasLayer(FIXED_INFOTEXT_LAYER_ZINDEX))
     {
       TextSceneLayer& layer = dynamic_cast<TextSceneLayer&>(
-        GetScene()->GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX));
+        controller_->GetScene().GetLayer(FIXED_INFOTEXT_LAYER_ZINDEX));
       layerP = &layer;
     }
     else
@@ -117,29 +107,29 @@
       layer->SetBorder(20);
       layer->SetAnchor(BitmapAnchor_TopLeft);
       //layer->SetPosition(0,0);
-      GetScene()->SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release());
+      controller_->GetScene().SetLayer(FIXED_INFOTEXT_LAYER_ZINDEX, layer.release());
     }
     // position the fixed info text in the upper right corner
     layerP->SetText(msgS.c_str());
     double cX = compositor_->GetCanvasWidth() * (-0.5);
     double cY = compositor_->GetCanvasHeight() * (-0.5);
-    GetScene()->GetCanvasToSceneTransform().Apply(cX,cY);
+    controller_->GetScene().GetCanvasToSceneTransform().Apply(cX,cY);
     layerP->SetPosition(cX, cY);
   }
 
   void TrackerSampleApp::DisplayFloatingCtrlInfoText(const PointerEvent& e)
   {
-    ScenePoint2D p = e.GetMainPosition().Apply(GetScene()->GetCanvasToSceneTransform());
+    ScenePoint2D p = e.GetMainPosition().Apply(controller_->GetScene().GetCanvasToSceneTransform());
 
     char buf[128];
     sprintf(buf, "S:(%0.02f,%0.02f) C:(%0.02f,%0.02f)", 
       p.GetX(), p.GetY(), 
       e.GetMainPosition().GetX(), e.GetMainPosition().GetY());
 
-    if (GetScene()->HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX))
+    if (controller_->GetScene().HasLayer(FLOATING_INFOTEXT_LAYER_ZINDEX))
     {
       TextSceneLayer& layer =
-        dynamic_cast<TextSceneLayer&>(GetScene()->GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX));
+        dynamic_cast<TextSceneLayer&>(controller_->GetScene().GetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX));
       layer.SetText(buf);
       layer.SetPosition(p.GetX(), p.GetY());
     }
@@ -151,13 +141,13 @@
       layer->SetBorder(20);
       layer->SetAnchor(BitmapAnchor_BottomCenter);
       layer->SetPosition(p.GetX(), p.GetY());
-      GetScene()->SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release());
+      controller_->GetScene().SetLayer(FLOATING_INFOTEXT_LAYER_ZINDEX, layer.release());
     }
   }
 
   void TrackerSampleApp::HideInfoText()
   {
-    GetScene()->DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX);
+    controller_->GetScene().DeleteLayer(FLOATING_INFOTEXT_LAYER_ZINDEX);
   }
 
   ScenePoint2D TrackerSampleApp::GetRandomPointInScene() const
@@ -179,7 +169,7 @@
     ScenePoint2D p = compositor_->GetPixelCenterCoordinates(x, y);
     LOG(TRACE) << "--> p.GetX() = " << p.GetX() << " p.GetY() = " << p.GetY();
 
-    ScenePoint2D r = p.Apply(GetScene()->GetCanvasToSceneTransform());
+    ScenePoint2D r = p.Apply(controller_->GetScene().GetCanvasToSceneTransform());
     LOG(TRACE) << "--> r.GetX() = " << r.GetX() << " r.GetY() = " << r.GetY();
     return r;
   }
@@ -276,7 +266,7 @@
         e.AddPosition(compositor_->GetPixelCenterCoordinates(event.button.x, event.button.y));
 
         ScenePoint2D scenePos = e.GetMainPosition().Apply(
-          controller_->GetScene()->GetCanvasToSceneTransform());
+          controller_->GetScene().GetCanvasToSceneTransform());
         //auto measureTools = GetController()->HitTestMeasureTools(scenePos);
         //LOG(TRACE) << "# of hit tests: " << measureTools.size();
         
@@ -527,13 +517,13 @@
       p[4] = 0;
       p[5] = 0;
 
-      GetScene()->SetLayer(TEXTURE_2x2_1_ZINDEX, new ColorTextureSceneLayer(i));
+      controller_->GetScene().SetLayer(TEXTURE_2x2_1_ZINDEX, new ColorTextureSceneLayer(i));
 
       std::auto_ptr<ColorTextureSceneLayer> l(new ColorTextureSceneLayer(i));
       l->SetOrigin(-3, 2);
       l->SetPixelSpacing(1.5, 1);
       l->SetAngle(20.0 / 180.0 * M_PI);
-      GetScene()->SetLayer(TEXTURE_2x2_2_ZINDEX, l.release());
+      controller_->GetScene().SetLayer(TEXTURE_2x2_2_ZINDEX, l.release());
     }
 
     // Texture of 1x1 size
@@ -548,7 +538,7 @@
       std::auto_ptr<ColorTextureSceneLayer> l(new ColorTextureSceneLayer(i));
       l->SetOrigin(-2, 1);
       l->SetAngle(20.0 / 180.0 * M_PI);
-      GetScene()->SetLayer(TEXTURE_1x1_ZINDEX, l.release());
+      controller_->GetScene().SetLayer(TEXTURE_1x1_ZINDEX, l.release());
     }
 
     // Some lines
@@ -579,14 +569,14 @@
       chain.push_back(ScenePoint2D(4, 2));
       layer->AddChain(chain, false, 0, 0, 255);
 
-      GetScene()->SetLayer(LINESET_1_ZINDEX, layer.release());
+      controller_->GetScene().SetLayer(LINESET_1_ZINDEX, layer.release());
     }
 
     // Some text
     {
       std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer);
       layer->SetText("Hello");
-      GetScene()->SetLayer(LINESET_2_ZINDEX, layer.release());
+      controller_->GetScene().SetLayer(LINESET_2_ZINDEX, layer.release());
     }
   }
 
@@ -623,7 +613,7 @@
   {
     // std::vector<boost::shared_ptr<MeasureTool>> measureTools_;
     ScenePoint2D scenePos = e.GetMainPosition().Apply(
-      controller_->GetScene()->GetCanvasToSceneTransform());
+      controller_->GetScene().GetCanvasToSceneTransform());
 
     std::vector<boost::shared_ptr<MeasureTool> > measureTools = controller_->HitTestMeasureTools(scenePos);
 
@@ -657,7 +647,7 @@
   {
     // False means we do NOT let Windows treat this as a legacy application
     // that needs to be scaled
-    SdlOpenGLWindow window("Hello", 1024, 1024, false);
+    SdlOpenGLContext window("Hello", 1024, 1024, false);
 
     controller_->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight());
 
@@ -687,7 +677,6 @@
           event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
         {
           DisableTracker(); // was: tracker.reset(NULL);
-          compositor_->UpdateSize();
         }
         else if (event.type == SDL_KEYDOWN &&
           event.key.repeat == 0 /* Ignore key bounce */)
--- a/Samples/Sdl/TrackerSampleApp.h	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.h	Tue Jul 16 12:37:29 2019 +0200
@@ -65,9 +65,6 @@
     void SetInfoDisplayMessage(std::string key, std::string value);
     void DisableTracker();
 
-    boost::shared_ptr<Scene2D> GetScene();
-    boost::shared_ptr<const Scene2D> GetScene() const;
-
     void HandleApplicationEvent(const SDL_Event& event);
 
     /**
--- a/Samples/Shared/SharedBasicScene.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/Shared/SharedBasicScene.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -225,7 +225,7 @@
   {
     if (showCursorInfo_)
     {
-      Scene2D& scene(*(viewportController_->GetScene()));
+      Scene2D& scene(viewportController_->GetScene());
       ShowCursorInfo(scene, pointerEvent);
     }
     return true;
@@ -247,7 +247,7 @@
 #if ORTHANC_SANDBOXED == 0
     case 'c':
     {
-      Scene2D& scene(*(viewportController_->GetScene()));
+      Scene2D& scene(viewportController_->GetScene());
       TakeScreenshot("screenshot.png", scene, compositor_->GetCanvasWidth(), compositor_->GetCanvasHeight());
       return true;
     }
@@ -257,7 +257,7 @@
       showCursorInfo_ = !showCursorInfo_;
       if (!showCursorInfo_)
       {
-        Scene2D& scene(*(viewportController_->GetScene()));
+        Scene2D& scene(viewportController_->GetScene());
         scene.DeleteLayer(BASIC_SCENE_LAYER_POSITION);
       }
 
--- a/Samples/WebAssembly/BasicScene.cpp	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/WebAssembly/BasicScene.cpp	Tue Jul 16 12:37:29 2019 +0200
@@ -33,12 +33,108 @@
 #include <Core/Logging.h>
 #include <Core/OrthancException.h>
 
-#include "../Shared/SharedBasicScene.h"
+void PrepareScene(OrthancStone::Scene2D& scene)
+{
+  using namespace OrthancStone;
+
+  // Texture of 2x2 size
+  if (1)
+  {
+    Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false);
+    
+    uint8_t *p = reinterpret_cast<uint8_t*>(i.GetRow(0));
+    p[0] = 255;
+    p[1] = 0;
+    p[2] = 0;
+
+    p[3] = 0;
+    p[4] = 255;
+    p[5] = 0;
+
+    p = reinterpret_cast<uint8_t*>(i.GetRow(1));
+    p[0] = 0;
+    p[1] = 0;
+    p[2] = 255;
+
+    p[3] = 255;
+    p[4] = 0;
+    p[5] = 0;
+
+    scene.SetLayer(12, new ColorTextureSceneLayer(i));
+
+    std::auto_ptr<ColorTextureSceneLayer> l(new ColorTextureSceneLayer(i));
+    l->SetOrigin(-3, 2);
+    l->SetPixelSpacing(1.5, 1);
+    l->SetAngle(20.0 / 180.0 * M_PI);
+    scene.SetLayer(14, l.release());
+  }
+
+  // Texture of 1x1 size
+  if (1)
+  {
+    Orthanc::Image i(Orthanc::PixelFormat_RGB24, 1, 1, false);
+    
+    uint8_t *p = reinterpret_cast<uint8_t*>(i.GetRow(0));
+    p[0] = 255;
+    p[1] = 0;
+    p[2] = 0;
+
+    std::auto_ptr<ColorTextureSceneLayer> l(new ColorTextureSceneLayer(i));
+    l->SetOrigin(-2, 1);
+    l->SetAngle(20.0 / 180.0 * M_PI);
+    scene.SetLayer(13, l.release());
+  }
+
+  // Some lines
+  if (1)
+  {
+    std::auto_ptr<PolylineSceneLayer> layer(new PolylineSceneLayer);
+
+    layer->SetThickness(1);
+
+    PolylineSceneLayer::Chain chain;
+    chain.push_back(ScenePoint2D(0 - 0.5, 0 - 0.5));
+    chain.push_back(ScenePoint2D(0 - 0.5, 2 - 0.5));
+    chain.push_back(ScenePoint2D(2 - 0.5, 2 - 0.5));
+    chain.push_back(ScenePoint2D(2 - 0.5, 0 - 0.5));
+    layer->AddChain(chain, true, 255, 0, 0);
+
+    chain.clear();
+    chain.push_back(ScenePoint2D(-5, -5));
+    chain.push_back(ScenePoint2D(5, -5));
+    chain.push_back(ScenePoint2D(5, 5));
+    chain.push_back(ScenePoint2D(-5, 5));
+    layer->AddChain(chain, true, 0, 255, 0);
+
+    double dy = 1.01;
+    chain.clear();
+    chain.push_back(ScenePoint2D(-4, -4));
+    chain.push_back(ScenePoint2D(4, -4 + dy));
+    chain.push_back(ScenePoint2D(-4, -4 + 2.0 * dy));
+    chain.push_back(ScenePoint2D(4, 2));
+    layer->AddChain(chain, false, 0, 0, 255);
+
+    scene.SetLayer(50, layer.release());
+  }
+
+  // Some text
+  if (1)
+  {
+    std::auto_ptr<TextSceneLayer> layer(new TextSceneLayer);
+    layer->SetText("Hello");
+    scene.SetLayer(100, layer.release());
+  }
+}
+
 
 std::auto_ptr<OrthancStone::WebAssemblyViewport>  viewport1_;
 std::auto_ptr<OrthancStone::WebAssemblyViewport>  viewport2_;
 std::auto_ptr<OrthancStone::WebAssemblyViewport>  viewport3_;
-OrthancStone::MessageBroker  broker_;
+boost::shared_ptr<OrthancStone::ViewportController>   controller1_;
+boost::shared_ptr<OrthancStone::ViewportController>   controller2_;
+boost::shared_ptr<OrthancStone::ViewportController>   controller3_;
+OrthancStone::MessageBroker broker_;
+
 
 EM_BOOL OnWindowResize(
   int eventType, const EmscriptenUiEvent *uiEvent, void *userData)
@@ -74,21 +170,41 @@
   EMSCRIPTEN_KEEPALIVE
   void Initialize()
   {
-    viewport1_.reset(
-      new OrthancStone::WebAssemblyViewport(broker_, "mycanvas1"));
+    viewport1_.reset(new OrthancStone::WebAssemblyViewport("mycanvas1"));
     PrepareScene(viewport1_->GetScene());
     viewport1_->UpdateSize();
 
-    viewport2_.reset(
-      new OrthancStone::WebAssemblyViewport(broker_, "mycanvas2"));
+    viewport2_.reset(new OrthancStone::WebAssemblyViewport("mycanvas2"));
     PrepareScene(viewport2_->GetScene());
     viewport2_->UpdateSize();
 
-    viewport3_.reset(
-      new OrthancStone::WebAssemblyViewport(broker_, "mycanvas3"));
+    viewport3_.reset(new OrthancStone::WebAssemblyViewport("mycanvas3"));
     PrepareScene(viewport3_->GetScene());
     viewport3_->UpdateSize();
 
+    viewport1_->GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, 
+                                        FONT_SIZE, Orthanc::Encoding_Latin1);
+    viewport2_->GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, 
+                                        FONT_SIZE, Orthanc::Encoding_Latin1);
+    viewport3_->GetCompositor().SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, 
+                                        FONT_SIZE, Orthanc::Encoding_Latin1);
+
+    controller1_.reset(new OrthancStone::ViewportController(boost::make_shared<OrthancStone::UndoStack>(), broker_, *viewport1_));
+    controller2_.reset(new OrthancStone::ViewportController(boost::make_shared<OrthancStone::UndoStack>(), broker_, *viewport2_));
+    controller3_.reset(new OrthancStone::ViewportController(boost::make_shared<OrthancStone::UndoStack>(), broker_, *viewport3_));
+
+    controller1_->FitContent(viewport1_->GetCanvasWidth(), viewport1_->GetCanvasHeight());
+    controller2_->FitContent(viewport2_->GetCanvasWidth(), viewport2_->GetCanvasHeight());
+    controller3_->FitContent(viewport3_->GetCanvasWidth(), viewport3_->GetCanvasHeight());
+
+    viewport1_->Refresh();
+    viewport2_->Refresh();
+    viewport3_->Refresh();
+
+    SetupEvents("mycanvas1", controller1_);
+    SetupEvents("mycanvas2", controller2_);
+    SetupEvents("mycanvas3", controller3_);
+
     emscripten_set_resize_callback("#window", NULL, false, OnWindowResize);
   }
 }
--- a/Samples/WebAssembly/CMakeLists.txt	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/WebAssembly/CMakeLists.txt	Tue Jul 16 12:37:29 2019 +0200
@@ -15,6 +15,8 @@
 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0")
 #set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXIT_RUNTIME=1")
 
+#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1")
+
 
 #####################################################################
 ## Configuration of the Orthanc framework
--- a/Samples/WebAssembly/dev.h	Fri Jul 12 14:57:48 2019 +0200
+++ b/Samples/WebAssembly/dev.h	Tue Jul 16 12:37:29 2019 +0200
@@ -21,95 +21,23 @@
 
 #pragma once
 
-#include "../../Framework/OpenGL/WebAssemblyOpenGLContext.h"
+#include "../../Framework/Viewport/WebAssemblyViewport.h"
 #include "../../Framework/Scene2D/OpenGLCompositor.h"
 #include "../../Framework/Scene2D/PanSceneTracker.h"
 #include "../../Framework/Scene2D/RotateSceneTracker.h"
 #include "../../Framework/Scene2D/ZoomSceneTracker.h"
+#include "../../Framework/Scene2DViewport/UndoStack.h"
 #include "../../Framework/Scene2DViewport/ViewportController.h"
-#include "../../Framework/Scene2DViewport/UndoStack.h"
 
 #include <Core/OrthancException.h>
-#include <boost/make_shared.hpp>
 
 #include <emscripten/html5.h>
+#include <boost/make_shared.hpp>
 
 static const unsigned int FONT_SIZE = 32;
 
 namespace OrthancStone
 {
-  class WebAssemblyViewport : public boost::noncopyable
-  {
-  private:
-    // the construction order is important because compositor_
-    // will hold a reference to the scene that belong to the 
-    // controller_ object
-    OpenGL::WebAssemblyOpenGLContext       context_;
-    boost::shared_ptr<ViewportController>  controller_;
-    OpenGLCompositor                       compositor_;
-
-    void SetupEvents(const std::string& canvas);
-
-  public:
-    WebAssemblyViewport(MessageBroker& broker,
-                        const std::string& canvas) :
-      context_(canvas),
-      controller_(new ViewportController(boost::make_shared<UndoStack>(), broker)),
-      compositor_(context_, *controller_->GetScene())
-    {
-      compositor_.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, 
-                          FONT_SIZE, Orthanc::Encoding_Latin1);
-      SetupEvents(canvas);
-    }
-
-    Scene2D& GetScene()
-    {
-      return *controller_->GetScene();
-    }
-
-    const boost::shared_ptr<ViewportController>& GetController()
-    {
-      return controller_;
-    }
-
-    void UpdateSize()
-    {
-      context_.UpdateSize();
-      compositor_.UpdateSize();
-      Refresh();
-    }
-
-    void Refresh()
-    {
-      compositor_.Refresh();
-    }
-
-    void FitContent()
-    {
-      GetScene().FitContent(context_.GetCanvasWidth(), context_.GetCanvasHeight());
-    }
-
-    const std::string& GetCanvasIdentifier() const
-    {
-      return context_.GetCanvasIdentifier();
-    }
-
-    ScenePoint2D GetPixelCenterCoordinates(int x, int y) const
-    {
-      return compositor_.GetPixelCenterCoordinates(x, y);
-    }
-
-    unsigned int GetCanvasWidth() const
-    {
-      return context_.GetCanvasWidth();
-    }
-
-    unsigned int GetCanvasHeight() const
-    {
-      return context_.GetCanvasHeight();
-    }
-  };
-
   class ActiveTracker : public boost::noncopyable
   {
   private:
@@ -119,9 +47,9 @@
     
   public:
     ActiveTracker(const boost::shared_ptr<IFlexiblePointerTracker>& tracker,
-                  const std::string& canvasIdentifier) :
+                  const std::string& canvasId) :
       tracker_(tracker),
-      canvasIdentifier_(canvasIdentifier),
+      canvasIdentifier_(canvasId),
       insideCanvas_(true)
     {
       if (tracker_.get() == NULL)
@@ -149,13 +77,13 @@
 
 static OrthancStone::PointerEvent* ConvertMouseEvent(
   const EmscriptenMouseEvent&        source,
-  OrthancStone::WebAssemblyViewport& viewport)
+  OrthancStone::IViewport& viewport)
 {
   std::auto_ptr<OrthancStone::PointerEvent> target(
     new OrthancStone::PointerEvent);
 
   target->AddPosition(viewport.GetPixelCenterCoordinates(
-    source.targetX, source.targetY));
+                        source.targetX, source.targetY));
   target->SetAltModifier(source.altKey);
   target->SetControlModifier(source.ctrlKey);
   target->SetShiftModifier(source.shiftKey);
@@ -172,8 +100,8 @@
   if (mouseEvent != NULL &&
       userData != NULL)
   {
-    OrthancStone::WebAssemblyViewport& viewport = 
-      *reinterpret_cast<OrthancStone::WebAssemblyViewport*>(userData);
+    boost::shared_ptr<OrthancStone::ViewportController>& controller = 
+      *reinterpret_cast<boost::shared_ptr<OrthancStone::ViewportController>*>(userData);
 
     switch (eventType)
     {
@@ -185,8 +113,8 @@
 
         std::auto_ptr<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer);
         layer->SetText(buf);
-        viewport.GetScene().SetLayer(100, layer.release());
-        viewport.Refresh();
+        controller->GetViewport().GetScene().SetLayer(100, layer.release());
+        controller->GetViewport().Refresh();
         break;
       }
 
@@ -196,27 +124,27 @@
 
         {
           std::auto_ptr<OrthancStone::PointerEvent> event(
-            ConvertMouseEvent(*mouseEvent, viewport));
+            ConvertMouseEvent(*mouseEvent, controller->GetViewport()));
 
           switch (mouseEvent->button)
           {
             case 0:  // Left button
               emscripten_console_log("Creating RotateSceneTracker");
               t.reset(new OrthancStone::RotateSceneTracker(
-                viewport.GetController(), *event));
+                        controller, *event));
               break;
 
             case 1:  // Middle button
               emscripten_console_log("Creating PanSceneTracker");
               LOG(INFO) << "Creating PanSceneTracker" ;
               t.reset(new OrthancStone::PanSceneTracker(
-                viewport.GetController(), *event));
+                        controller, *event));
               break;
 
             case 2:  // Right button
               emscripten_console_log("Creating ZoomSceneTracker");
               t.reset(new OrthancStone::ZoomSceneTracker(
-                viewport.GetController(), *event, viewport.GetCanvasWidth()));
+                        controller, *event, controller->GetViewport().GetCanvasWidth()));
               break;
 
             default:
@@ -227,8 +155,8 @@
         if (t.get() != NULL)
         {
           tracker_.reset(
-            new OrthancStone::ActiveTracker(t, viewport.GetCanvasIdentifier()));
-          viewport.Refresh();
+            new OrthancStone::ActiveTracker(t, controller->GetViewport().GetCanvasIdentifier()));
+          controller->GetViewport().Refresh();
         }
 
         break;
@@ -238,9 +166,9 @@
         if (tracker_.get() != NULL)
         {
           std::auto_ptr<OrthancStone::PointerEvent> event(
-            ConvertMouseEvent(*mouseEvent, viewport));
+            ConvertMouseEvent(*mouseEvent, controller->GetViewport()));
           tracker_->PointerMove(*event);
-          viewport.Refresh();
+          controller->GetViewport().Refresh();
         }
         break;
 
@@ -248,9 +176,9 @@
         if (tracker_.get() != NULL)
         {
           std::auto_ptr<OrthancStone::PointerEvent> event(
-            ConvertMouseEvent(*mouseEvent, viewport));
+            ConvertMouseEvent(*mouseEvent, controller->GetViewport()));
           tracker_->PointerUp(*event);
-          viewport.Refresh();
+          controller->GetViewport().Refresh();
           if (!tracker_->IsAlive())
             tracker_.reset();
         }
@@ -265,9 +193,10 @@
 }
 
 
-void OrthancStone::WebAssemblyViewport::SetupEvents(const std::string& canvas)
+void SetupEvents(const std::string& canvas,
+                 boost::shared_ptr<OrthancStone::ViewportController>& controller)
 {
-  emscripten_set_mousedown_callback(canvas.c_str(), this, false, OnMouseEvent);
-  emscripten_set_mousemove_callback(canvas.c_str(), this, false, OnMouseEvent);
-  emscripten_set_mouseup_callback(canvas.c_str(), this, false, OnMouseEvent);
+  emscripten_set_mousedown_callback(canvas.c_str(), &controller, false, OnMouseEvent);
+  emscripten_set_mousemove_callback(canvas.c_str(), &controller, false, OnMouseEvent);
+  emscripten_set_mouseup_callback(canvas.c_str(), &controller, false, OnMouseEvent);
 }