changeset 891:0aff28f15ea2

new abstraction: IViewport
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jul 2019 18:18:42 +0200
parents 77c96ba899f9
children 50cd372e2460
files Applications/Sdl/SdlOpenGLContext.cpp Applications/Sdl/SdlOpenGLContext.h Applications/Sdl/SdlOpenGLWindow.cpp Applications/Sdl/SdlOpenGLWindow.h Framework/OpenGL/WebAssemblyOpenGLContext.cpp Framework/OpenGL/WebAssemblyOpenGLContext.h Framework/Scene2D/OpenGLCompositor.cpp Framework/Scene2D/OpenGLCompositor.h Framework/Scene2DViewport/AngleMeasureTool.cpp Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp Framework/Scene2DViewport/CreateLineMeasureTracker.cpp Framework/Scene2DViewport/EditAngleMeasureTracker.cpp Framework/Scene2DViewport/EditLineMeasureTracker.cpp Framework/Scene2DViewport/LayerHolder.cpp Framework/Scene2DViewport/LayerHolder.h Framework/Scene2DViewport/LineMeasureTool.cpp Framework/Scene2DViewport/MeasureTool.cpp Framework/Scene2DViewport/MeasureTool.h Framework/Scene2DViewport/MeasureToolsToolbox.h Framework/Scene2DViewport/MeasureTrackers.cpp Framework/Scene2DViewport/MeasureTrackers.h Framework/Scene2DViewport/ViewportController.cpp Framework/Scene2DViewport/ViewportController.h Framework/Viewport/IViewport.h Framework/Viewport/SdlViewport.cpp Framework/Viewport/SdlViewport.h Framework/Viewport/ViewportBase.cpp Framework/Viewport/ViewportBase.h Framework/Viewport/WebAssemblyViewport.cpp Framework/Viewport/WebAssemblyViewport.h Resources/CMake/OrthancStoneConfiguration.cmake Samples/Sdl/BasicScene.cpp Samples/Sdl/FusionMprSdl.cpp Samples/Sdl/TrackerSample.cpp Samples/Sdl/TrackerSampleApp.cpp Samples/Sdl/TrackerSampleApp.h Samples/WebAssembly/BasicScene.cpp Samples/WebAssembly/dev.h
diffstat 38 files changed, 816 insertions(+), 467 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Applications/Sdl/SdlOpenGLContext.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 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	Wed Jul 10 15:23:13 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/OpenGL/WebAssemblyOpenGLContext.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/OpenGL/WebAssemblyOpenGLContext.h	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2D/OpenGLCompositor.cpp	Wed Jul 10 18:18:42 2019 +0200
@@ -200,12 +200,4 @@
     SetFont(index, dict);
   }
 #endif
-
-
-  ScenePoint2D OpenGLCompositor::GetPixelCenterCoordinates(int x, int y) const
-  {
-    return ScenePoint2D(
-      static_cast<double>(x) + 0.5 - static_cast<double>(canvasWidth_) / 2.0,
-      static_cast<double>(y) + 0.5 - static_cast<double>(canvasHeight_) / 2.0);
-  }
 }
--- a/Framework/Scene2D/OpenGLCompositor.h	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2D/OpenGLCompositor.h	Wed Jul 10 18:18:42 2019 +0200
@@ -77,8 +77,5 @@
     {
       return canvasHeight_;
     }
-
-    // TODO => REMOVE
-    ScenePoint2D GetPixelCenterCoordinates(int x, int y) const;
   };
 }
--- a/Framework/Scene2DViewport/AngleMeasureTool.cpp	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/AngleMeasureTool.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/CreateAngleMeasureTracker.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/CreateLineMeasureTracker.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/EditAngleMeasureTracker.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/EditLineMeasureTracker.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/LayerHolder.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/LayerHolder.h	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/LineMeasureTool.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTool.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTool.h	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureToolsToolbox.h	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTrackers.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/MeasureTrackers.h	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Framework/Scene2DViewport/ViewportController.h	Wed Jul 10 18:18:42 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	Wed Jul 10 18:18:42 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	Wed Jul 10 18:18:42 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	Wed Jul 10 18:18:42 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	Wed Jul 10 18:18:42 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	Wed Jul 10 18:18:42 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	Wed Jul 10 18:18:42 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	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Resources/CMake/OrthancStoneConfiguration.cmake	Wed Jul 10 18:18:42 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/Sdl/BasicScene.cpp	Wed Jul 10 15:23:13 2019 +0200
+++ b/Samples/Sdl/BasicScene.cpp	Wed Jul 10 18:18:42 2019 +0200
@@ -20,13 +20,12 @@
 
 
 // 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/Scene2D.h"
 #include "../../Framework/Scene2D/ZoomSceneTracker.h"
 #include "../../Framework/Scene2DViewport/ViewportController.h"
 #include "../../Framework/Scene2DViewport/UndoStack.h"
@@ -42,7 +41,6 @@
 #include <Core/Images/PngWriter.h>
 
 #include <boost/make_shared.hpp>
-#include <boost/ref.hpp>
 
 #include <SDL.h>
 #include <stdio.h>
@@ -50,10 +48,11 @@
 static const unsigned int FONT_SIZE = 32;
 static const int LAYER_POSITION = 150;
 
-void PrepareScene(boost::shared_ptr<OrthancStone::ViewportController> controller)
+
+void PrepareScene(OrthancStone::Scene2D& scene)
 {
   using namespace OrthancStone;
-  Scene2D& scene(*controller->GetScene());
+
   // Texture of 2x2 size
   {
     Orthanc::Image i(Orthanc::PixelFormat_RGB24, 2, 2, false);
@@ -162,13 +161,15 @@
 }
 
 
-void HandleApplicationEvent(boost::shared_ptr<OrthancStone::ViewportController> controller,
-                            const OrthancStone::OpenGLCompositor& compositor,
-                            const SDL_Event& event,
+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());
+
+  Scene2D& scene = controller->GetScene();
+  IViewport& viewport = controller->GetViewport();
+
   if (event.type == SDL_MOUSEMOTION)
   {
     int scancodeCount = 0;
@@ -181,7 +182,7 @@
       // The "left-ctrl" key is down, while no tracker is present
 
       PointerEvent e;
-      e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
+      e.AddPosition(viewport.GetPixelCenterCoordinates(event.button.x, event.button.y));
 
       ScenePoint2D p = e.GetMainPosition().Apply(scene.GetCanvasToSceneTransform());
 
@@ -215,7 +216,7 @@
   else if (event.type == SDL_MOUSEBUTTONDOWN)
   {
     PointerEvent e;
-    e.AddPosition(compositor.GetPixelCenterCoordinates(event.button.x, event.button.y));
+    e.AddPosition(viewport.GetPixelCenterCoordinates(event.button.x, event.button.y));
 
     switch (event.button.button)
     {
@@ -224,8 +225,8 @@
         break;
 
       case SDL_BUTTON_RIGHT:
-        activeTracker = boost::make_shared<ZoomSceneTracker>(controller, 
-          e, compositor.GetCanvasHeight());
+        activeTracker = boost::make_shared<ZoomSceneTracker>
+          (controller, e, viewport.GetCanvasHeight());
         break;
 
       case SDL_BUTTON_LEFT:
@@ -242,14 +243,14 @@
     switch (event.key.keysym.sym)
     {
       case SDLK_s:
-        controller->FitContent(compositor.GetCanvasWidth(), 
-                         compositor.GetCanvasHeight());
+        controller->FitContent(viewport.GetCanvasWidth(), 
+                               viewport.GetCanvasHeight());
         break;
               
       case SDLK_c:
         TakeScreenshot("screenshot.png", scene, 
-                       compositor.GetCanvasWidth(), 
-                       compositor.GetCanvasHeight());
+                       viewport.GetCanvasWidth(), 
+                       viewport.GetCanvasHeight());
         break;
               
       default:
@@ -277,26 +278,24 @@
 }
 
 
-void Run(boost::shared_ptr<OrthancStone::ViewportController> controller)
+void Run(OrthancStone::MessageBroker& broker,
+         OrthancStone::SdlViewport& viewport)
 {
   using namespace OrthancStone;
-  SdlOpenGLWindow window("Hello", 1024, 768);
-
-  controller->FitContent(window.GetCanvasWidth(), window.GetCanvasHeight());
+  
+  boost::shared_ptr<ViewportController> controller(
+    new ViewportController(boost::make_shared<UndoStack>(), broker, viewport));
   
   glEnable(GL_DEBUG_OUTPUT);
   glDebugMessageCallback(OpenGLMessageCallback, 0);
 
-  OpenGLCompositor compositor(window, *controller->GetScene());
-  compositor.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, 
-                     FONT_SIZE, Orthanc::Encoding_Latin1);
-
   boost::shared_ptr<IFlexiblePointerTracker> tracker;
 
+  bool firstShown = true;
   bool stop = false;
   while (!stop)
   {
-    compositor.Refresh();
+    viewport.Refresh();
 
     SDL_Event event;
     while (!stop &&
@@ -312,7 +311,7 @@
         if (tracker)
         {
           PointerEvent e;
-          e.AddPosition(compositor.GetPixelCenterCoordinates(
+          e.AddPosition(viewport.GetPixelCenterCoordinates(
             event.button.x, event.button.y));
           tracker->PointerMove(e);
         }
@@ -322,17 +321,34 @@
         if (tracker)
         {
           PointerEvent e;
-          e.AddPosition(compositor.GetPixelCenterCoordinates(
+          e.AddPosition(viewport.GetPixelCenterCoordinates(
             event.button.x, event.button.y));
           tracker->PointerUp(e);
           if(!tracker->IsAlive())
             tracker.reset();
         }
       }
-      else if (event.type == SDL_WINDOWEVENT &&
-               event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
+      else if (event.type == SDL_WINDOWEVENT)
       {
-        tracker.reset();
+        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 */)
@@ -340,7 +356,7 @@
         switch (event.key.keysym.sym)
         {
           case SDLK_f:
-            window.GetWindow().ToggleMaximize();
+            viewport.GetContext().GetWindow().ToggleMaximize();
             break;
               
           case SDLK_q:
@@ -352,7 +368,7 @@
         }
       }
       
-      HandleApplicationEvent(controller, compositor, event, tracker);
+      HandleApplicationEvent(event, controller, tracker);
     }
 
     SDL_Delay(1);
@@ -369,25 +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));
-    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/FusionMprSdl.cpp	Wed Jul 10 15:23:13 2019 +0200
+++ b/Samples/Sdl/FusionMprSdl.cpp	Wed Jul 10 18:18:42 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());
 
--- a/Samples/Sdl/TrackerSample.cpp	Wed Jul 10 15:23:13 2019 +0200
+++ b/Samples/Sdl/TrackerSample.cpp	Wed Jul 10 18:18:42 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	Wed Jul 10 15:23:13 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.cpp	Wed Jul 10 18:18:42 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());
 
--- a/Samples/Sdl/TrackerSampleApp.h	Wed Jul 10 15:23:13 2019 +0200
+++ b/Samples/Sdl/TrackerSampleApp.h	Wed Jul 10 18:18:42 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/WebAssembly/BasicScene.cpp	Wed Jul 10 15:23:13 2019 +0200
+++ b/Samples/WebAssembly/BasicScene.cpp	Wed Jul 10 18:18:42 2019 +0200
@@ -129,7 +129,11 @@
 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)
@@ -165,21 +169,33 @@
   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_));
+
+    SetupEvents("mycanvas1", controller1_);
+    SetupEvents("mycanvas2", controller2_);
+    SetupEvents("mycanvas3", controller3_);
+
     emscripten_set_resize_callback("#window", NULL, false, OnWindowResize);
   }
 }
--- a/Samples/WebAssembly/dev.h	Wed Jul 10 15:23:13 2019 +0200
+++ b/Samples/WebAssembly/dev.h	Wed Jul 10 18:18:42 2019 +0200
@@ -21,7 +21,7 @@
 
 #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"
@@ -38,78 +38,6 @@
 
 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 WebAssemblyViewport& viewport) :
+                  const std::string& canvasId) :
       tracker_(tracker),
-      canvasIdentifier_(viewport.GetCanvasIdentifier()),
+      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));
-          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);
 }