# HG changeset patch # User Alain Mazy # Date 1643456852 -3600 # Node ID 917500c46fe03e5a528dfd62079ffbd9aa587617 # Parent a5e54bd87b25506bee16683e9032d98422820639 moved the Platform folder from the Applications folder to the Stone library itself diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/Sdl/OrthancStoneSdlConfiguration.cmake --- a/Applications/Platforms/Sdl/OrthancStoneSdlConfiguration.cmake Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -# Stone of Orthanc -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2022 Osimis S.A., Belgium -# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - - - -##################################################################### -## Sanity check of the configuration -##################################################################### - -include(${ORTHANC_STONE_ROOT}/../Resources/CMake/OrthancStoneConfiguration.cmake) - -if (ORTHANC_SANDBOXED) - message(FATAL_ERROR "Cannot enable SDL in sandboxed environments") -endif() - -if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR - CMAKE_SYSTEM_NAME STREQUAL "PNaCl" OR - CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR - CMAKE_SYSTEM_NAME STREQUAL "NaCl64") - message(FATAL_ERROR "Trying to use a Web compiler for a native build") -endif() - - - -##################################################################### -## Configure SDL -##################################################################### - -message("SDL is enabled") -include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake) - -add_definitions( - -DORTHANC_ENABLE_SDL=1 - -DORTHANC_ENABLE_WASM=0 - ) - -if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND - NOT MSVC) - # This is necessary when compiling EXE for Windows using MinGW - link_libraries(mingw32) -endif() - - - -##################################################################### -## Additional source files for SDL -##################################################################### - -list(APPEND ORTHANC_STONE_SOURCES - ${CMAKE_CURRENT_LIST_DIR}/SdlViewport.cpp - ${CMAKE_CURRENT_LIST_DIR}/SdlWindow.cpp - ${SDL_SOURCES} - ) - -if (ENABLE_OPENGL) - list(APPEND ORTHANC_STONE_SOURCES - ${CMAKE_CURRENT_LIST_DIR}/SdlOpenGLContext.cpp - ) -endif() diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/Sdl/OrthancStoneSdlParameters.cmake --- a/Applications/Platforms/Sdl/OrthancStoneSdlParameters.cmake Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -# Stone of Orthanc -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2022 Osimis S.A., Belgium -# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - - - -##################################################################### -## Load generic parameters -##################################################################### - -include(${CMAKE_CURRENT_LIST_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake) - - -##################################################################### -## CMake parameters tunable by the user -##################################################################### - -# Advanced parameters to fine-tune linking against system libraries -set(USE_SYSTEM_SDL ON CACHE BOOL "Use the system version of SDL2") - - -##################################################################### -## Internal CMake parameters to enable the optional subcomponents of -## the Stone of Orthanc -##################################################################### - -set(ENABLE_THREADS ON CACHE INTERNAL "") diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/Sdl/SdlConfiguration.cmake --- a/Applications/Platforms/Sdl/SdlConfiguration.cmake Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,225 +0,0 @@ -# Stone of Orthanc -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2022 Osimis S.A., Belgium -# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - - -if (STATIC_BUILD OR NOT USE_SYSTEM_SDL) - SET(SDL_SOURCES_DIR ${CMAKE_BINARY_DIR}/SDL2-2.0.4) - SET(SDL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/SDL2-2.0.4.tar.gz") - SET(SDL_MD5 "44fc4a023349933e7f5d7a582f7b886e") - DownloadPackage(${SDL_MD5} ${SDL_URL} "${SDL_SOURCES_DIR}") - - include_directories(${SDL_SOURCES_DIR}/include) - - set(TMP "${SDL_SOURCES_DIR}/include/SDL_config_premake.h") - if (NOT EXISTS "${TMP}") - file(WRITE "${TMP}" " -#include \"SDL_platform.h\" -#define HAVE_STDARG_H 1 -#define HAVE_STDDEF_H 1 -#define HAVE_STDINT_H 1 -") - endif() - - # General source files - file(GLOB SDL_SOURCES - ${SDL_SOURCES_DIR}/src/*.c - ${SDL_SOURCES_DIR}/src/atomic/*.c - ${SDL_SOURCES_DIR}/src/audio/*.c - ${SDL_SOURCES_DIR}/src/cpuinfo/*.c - ${SDL_SOURCES_DIR}/src/dynapi/*.c - ${SDL_SOURCES_DIR}/src/events/*.c - ${SDL_SOURCES_DIR}/src/file/*.c - ${SDL_SOURCES_DIR}/src/haptic/*.c - ${SDL_SOURCES_DIR}/src/joystick/*.c - ${SDL_SOURCES_DIR}/src/libm/*.c - ${SDL_SOURCES_DIR}/src/power/*.c - ${SDL_SOURCES_DIR}/src/render/*.c - ${SDL_SOURCES_DIR}/src/stdlib/*.c - ${SDL_SOURCES_DIR}/src/thread/*.c - ${SDL_SOURCES_DIR}/src/timer/*.c - ${SDL_SOURCES_DIR}/src/video/*.c - - ${SDL_SOURCES_DIR}/src/loadso/dummy/*.c - #${SDL_SOURCES_DIR}/src/timer/dummy/*.c - ${SDL_SOURCES_DIR}/src/audio/dummy/*.c - ${SDL_SOURCES_DIR}/src/filesystem/dummy/*.c - ${SDL_SOURCES_DIR}/src/haptic/dummy/*.c - ${SDL_SOURCES_DIR}/src/joystick/dummy/*.c - #${SDL_SOURCES_DIR}/src/main/dummy/*.c - ${SDL_SOURCES_DIR}/src/video/dummy/*.c - ) - - add_definitions( - -DUSING_PREMAKE_CONFIG_H=1 - - -DSDL_AUDIO_DISABLED=1 - -DSDL_AUDIO_DRIVER_DUMMY=1 - -DSDL_FILESYSTEM_DISABLED=1 - -DSDL_FILESYSTEM_DUMMY=1 - -DSDL_FILE_DISABLED=1 - -DSDL_HAPTIC_DISABLED=1 - -DSDL_JOYSTICK_DISABLED=1 - - #-DSDL_THREADS_DISABLED=1 - ) - - if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - file(GLOB TMP - ${SDL_SOURCES_DIR}/src/core/linux/*.c - ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c - ${SDL_SOURCES_DIR}/src/render/software/*.c - ${SDL_SOURCES_DIR}/src/thread/pthread/*.c - ${SDL_SOURCES_DIR}/src/timer/unix/*.c - ${SDL_SOURCES_DIR}/src/video/x11/*.c - ) - - list(APPEND SDL_SOURCES ${TMP}) - - add_definitions( - -DSDL_LOADSO_DLOPEN=1 - -DSDL_THREAD_PTHREAD=1 - -DSDL_TIMER_UNIX=1 - -DSDL_POWER_DISABLED=1 - - -DSDL_VIDEO_DRIVER_X11=1 - - -DSDL_ASSEMBLY_ROUTINES=1 - -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1 - -DSDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS=1 - -DHAVE_GCC_SYNC_LOCK_TEST_AND_SET=1 - ) - - link_libraries(X11 Xext) - - if (NOT CMAKE_SYSTEM_VERSION STREQUAL "Raspberry") - # Raspberry Pi has no support for OpenGL - file(GLOB TMP - ${SDL_SOURCES_DIR}/src/render/opengl/*.c - ${SDL_SOURCES_DIR}/src/render/opengles2/*.c - ) - - list(APPEND SDL_SOURCES ${TMP}) - - add_definitions( - -DSDL_VIDEO_OPENGL=1 - -DSDL_VIDEO_OPENGL_ES2=1 - -DSDL_VIDEO_RENDER_OGL=1 - -DSDL_VIDEO_RENDER_OGL_ES2=1 - -DSDL_VIDEO_OPENGL_GLX=1 - -DSDL_VIDEO_OPENGL_EGL=1 - ) - endif() - - elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") - file(GLOB TMP - ${SDL_SOURCES_DIR}/src/audio/directsound/*.c - ${SDL_SOURCES_DIR}/src/audio/disk/*.c - ${SDL_SOURCES_DIR}/src/audio/winmm/*.c - ${SDL_SOURCES_DIR}/src/joystick/windows/*.c - ${SDL_SOURCES_DIR}/src/haptic/windows/*.c - ${SDL_SOURCES_DIR}/src/power/windows/*.c - - ${SDL_SOURCES_DIR}/src/main/windows/*.c - ${SDL_SOURCES_DIR}/src/core/windows/*.c - ${SDL_SOURCES_DIR}/src/loadso/windows/*.c - ${SDL_SOURCES_DIR}/src/render/direct3d/*.c - ${SDL_SOURCES_DIR}/src/render/direct3d11/*.c - ${SDL_SOURCES_DIR}/src/render/opengl/*.c - ${SDL_SOURCES_DIR}/src/render/psp/*.c - ${SDL_SOURCES_DIR}/src/render/opengles/*.c - ${SDL_SOURCES_DIR}/src/render/opengles2/*.c - ${SDL_SOURCES_DIR}/src/render/software/*.c - ${SDL_SOURCES_DIR}/src/thread/generic/SDL_syscond.c # Don't include more files from "thread/generic/*.c"! - ${SDL_SOURCES_DIR}/src/thread/windows/*.c - ${SDL_SOURCES_DIR}/src/timer/windows/*.c - ${SDL_SOURCES_DIR}/src/video/windows/*.c - ${SDL_SOURCES_DIR}/src/windows/dlopen/*.c - ) - - list(APPEND SDL_SOURCES ${TMP}) - - # NB: OpenGL ES headers are not available in MinGW-W64 - add_definitions( - -DSDL_LOADSO_WINDOWS=1 - -DSDL_THREAD_WINDOWS=1 - -DSDL_TIMER_WINDOWS=1 - -DSDL_POWER_WINDOWS=1 - - -DSDL_VIDEO_OPENGL=1 - -DSDL_VIDEO_OPENGL_WGL=1 - -DSDL_VIDEO_RENDER_D3D=1 - -DSDL_VIDEO_RENDER_OGL=1 - -DSDL_VIDEO_DRIVER_WINDOWS=1 - ) - - if (MSVC) - add_definitions( - -D__FLTUSED__ - -DHAVE_LIBC=1 - ) - else() - add_definitions( - -DHAVE_GCC_ATOMICS=1 - -DSDL_ASSEMBLY_ROUTINES=1 - ) - endif() - - link_libraries(imm32 winmm version) - - elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") - file(GLOB TMP - ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c - ${SDL_SOURCES_DIR}/src/render/opengl/*.c - ${SDL_SOURCES_DIR}/src/render/opengles2/*.c - ${SDL_SOURCES_DIR}/src/render/software/*.c - ${SDL_SOURCES_DIR}/src/thread/pthread/*.c - ${SDL_SOURCES_DIR}/src/timer/unix/*.c - ${SDL_SOURCES_DIR}/src/video/cocoa/*.m - ) - - list(APPEND SDL_SOURCES ${TMP}) - - add_definitions( - -DSDL_LOADSO_DLOPEN=1 - -DSDL_THREAD_PTHREAD=1 - -DSDL_TIMER_UNIX=1 - -DSDL_POWER_DISABLED=1 - - -DSDL_VIDEO_DRIVER_COCOA=1 - -DSDL_VIDEO_OPENGL=1 - -DSDL_VIDEO_OPENGL_CGL=1 - -DSDL_VIDEO_RENDER_OGL=1 - - -DSDL_ASSEMBLY_ROUTINES=1 - -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1 - ) - - find_library(CARBON_LIBRARY Carbon) - find_library(COCOA_LIBRARY Cocoa) - find_library(IOKIT_LIBRARY IOKit) - find_library(QUARTZ_LIBRARY QuartzCore) - link_libraries(${CARBON_LIBRARY} ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${QUARTZ_LIBRARY}) - - endif() - -else() - pkg_search_module(SDL2 REQUIRED sdl2) - include_directories(${SDL2_INCLUDE_DIRS}) - link_libraries(${SDL2_LIBRARIES}) -endif() diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/Sdl/SdlOpenGLContext.cpp --- a/Applications/Platforms/Sdl/SdlOpenGLContext.cpp Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,131 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#include "SdlOpenGLContext.h" -#include "../../../OrthancStone/Sources/StoneException.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 -#endif - -#include -#include - -#include - -namespace OrthancStone -{ - SdlOpenGLContext::SdlOpenGLContext(const char* title, - unsigned int width, - unsigned int height, - bool allowDpiScaling) - : window_(title, width, height, true /* enable OpenGL */, allowDpiScaling) - , context_(NULL) - { - 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) - { - LOG(ERROR) << glewGetErrorString(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) - { - const char* errText = SDL_GetError(); - std::stringstream ss; - ss << "Cannot set current OpenGL context. SDL error text: " << errText; - std::string errStr = ss.str(); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, errStr.c_str()); - } - - // This makes our buffer swap synchronized 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(w); - } - - - unsigned int SdlOpenGLContext::GetCanvasHeight() const - { - int h = 0; - SDL_GL_GetDrawableSize(window_.GetObject(), NULL, &h); - return static_cast(h); - } -} - -#endif diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/Sdl/SdlOpenGLContext.h --- a/Applications/Platforms/Sdl/SdlOpenGLContext.h Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#pragma once - -#if ORTHANC_ENABLE_SDL == 1 - -#include "../../../OrthancStone/Sources/OpenGL/IOpenGLContext.h" -#include "SdlWindow.h" - -#include // For ORTHANC_OVERRIDE - -#include - -#include - -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 bool IsContextLost() ORTHANC_OVERRIDE - { - // On desktop applications, an OpenGL context should never be lost - return false; - } - - virtual void MakeCurrent() ORTHANC_OVERRIDE; - - virtual void SwapBuffer() ORTHANC_OVERRIDE; - - unsigned int GetCanvasWidth() const; - - unsigned int GetCanvasHeight() const; - - void ToggleMaximize() - { - window_.ToggleMaximize(); - } - }; -} - -#endif diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/Sdl/SdlViewport.cpp --- a/Applications/Platforms/Sdl/SdlViewport.cpp Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,263 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - -#include "SdlViewport.h" - -#include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" - -#include - -#include - -namespace OrthancStone -{ - ICompositor& SdlViewport::SdlLock::GetCompositor() - { - if (that_.compositor_.get() == NULL) - { - // The derived class should have called "AcquireCompositor()" - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - else - { - return *that_.compositor_; - } - } - - - void SdlViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */) - { - if (compositor == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - compositor_.reset(compositor); - } - - - SdlViewport::SdlViewport() - { - refreshEvent_ = SDL_RegisterEvents(1); - - if (refreshEvent_ == static_cast(-1)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - void SdlViewport::PostConstructor() - { - controller_ = boost::make_shared(shared_from_this()); - } - - void SdlViewport::SendRefreshEvent() - { - SDL_Event event; - SDL_memset(&event, 0, sizeof(event)); - event.type = refreshEvent_; - SDL_PushEvent(&event); // This function is thread-safe, and can be called from other threads safely. - } - - - void SdlViewport::UpdateSize(unsigned int width, unsigned int height) - { - SdlLock lock(*this); - lock.GetCompositor().SetCanvasSize(width, height); - lock.Invalidate(); - } - - -#if ORTHANC_ENABLE_OPENGL == 1 - SdlOpenGLViewport::SdlOpenGLViewport(const std::string& title, - unsigned int width, - unsigned int height, - bool allowDpiScaling) : - context_(title.c_str(), width, height, allowDpiScaling) - { - AcquireCompositor(new OpenGLCompositor(context_)); // (*) - } -#endif - - -#if ORTHANC_ENABLE_OPENGL == 1 - void SdlOpenGLViewport::RefreshCanvasSize() - { - UpdateSize(context_.GetCanvasWidth(), context_.GetCanvasHeight()); - } -#endif - - -#if ORTHANC_ENABLE_OPENGL == 1 - boost::shared_ptr SdlOpenGLViewport::Create(const std::string& title, - unsigned int width, - unsigned int height, - bool allowDpiScaling) - { - boost::shared_ptr that = - boost::shared_ptr(new SdlOpenGLViewport(title, width, height, allowDpiScaling)); - that->SdlViewport::PostConstructor(); - return that; - } -#endif - - -#if ORTHANC_ENABLE_OPENGL == 1 - uint32_t SdlOpenGLViewport::GetSdlWindowId() - { - const SdlWindow& sdlWindowWrapper = context_.GetWindow(); - SDL_Window* sdlWindow = sdlWindowWrapper.GetObject(); - Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow); - return sdlWindowId; - } -#endif - - -#if ORTHANC_ENABLE_OPENGL == 1 - SdlOpenGLViewport::~SdlOpenGLViewport() - { - // Make sure that the "OpenGLCompositor" is destroyed BEFORE the - // "OpenGLContext" it references (*) - ClearCompositor(); - } -#endif - - -#if ORTHANC_ENABLE_OPENGL == 1 - void SdlOpenGLViewport::Paint() - { - SdlLock lock(*this); - lock.GetCompositor().Refresh(lock.GetController().GetScene()); - } -#endif - - -#if ORTHANC_ENABLE_OPENGL == 1 - void SdlOpenGLViewport::ToggleMaximize() - { - // No need to call "Invalidate()" here, as "UpdateSize()" will - // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED" - SdlLock lock(*this); - context_.ToggleMaximize(); - } -#endif - - - void SdlCairoViewport::RefreshCanvasSize() - { - UpdateSize(window_.GetWidth(), window_.GetHeight()); - } - - SdlCairoViewport::SdlCairoViewport(const std::string& title, - unsigned int width, - unsigned int height, - bool allowDpiScaling) : - window_(title, width, height, false /* enable OpenGL */, allowDpiScaling), - sdlSurface_(NULL) - { - AcquireCompositor(new CairoCompositor(width, height)); - } - - SdlCairoViewport::~SdlCairoViewport() - { - if (sdlSurface_) - { - SDL_FreeSurface(sdlSurface_); - } - } - - uint32_t SdlCairoViewport::GetSdlWindowId() - { - SDL_Window* sdlWindow = window_.GetObject(); - Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow); - return sdlWindowId; - } - - void SdlCairoViewport::Paint() - { - SdlLock lock(*this); - - lock.GetCompositor().Refresh(lock.GetController().GetScene()); - CreateSdlSurfaceFromCompositor(dynamic_cast(lock.GetCompositor())); - - if (sdlSurface_ != NULL) - { - window_.Render(sdlSurface_); - } - } - - - void SdlCairoViewport::ToggleMaximize() - { - // No need to call "Invalidate()" here, as "UpdateSize()" will - // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED" - SdlLock lock(*this); - window_.ToggleMaximize(); - } - - - // Assumes that the mutex is locked - void SdlCairoViewport::CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor) - { - static const uint32_t rmask = 0x00ff0000; - static const uint32_t gmask = 0x0000ff00; - static const uint32_t bmask = 0x000000ff; - - const unsigned int width = compositor.GetCanvas().GetWidth(); - const unsigned int height = compositor.GetCanvas().GetHeight(); - - if (sdlSurface_ != NULL) - { - if (sdlSurface_->pixels == compositor.GetCanvas().GetBuffer() && - sdlSurface_->w == static_cast(width) && - sdlSurface_->h == static_cast(height) && - sdlSurface_->pitch == static_cast(compositor.GetCanvas().GetPitch())) - { - // The image from the compositor has not changed, no need to update the surface - return; - } - else - { - SDL_FreeSurface(sdlSurface_); - } - } - - sdlSurface_ = SDL_CreateRGBSurfaceFrom((void*)(compositor.GetCanvas().GetBuffer()), width, height, 32, - compositor.GetCanvas().GetPitch(), rmask, gmask, bmask, 0); - if (!sdlSurface_) - { - LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - boost::shared_ptr SdlCairoViewport::Create(const std::string& title, - unsigned int width, - unsigned int height, - bool allowDpiScaling) - { - boost::shared_ptr that = - boost::shared_ptr(new SdlCairoViewport(title, width, height, allowDpiScaling)); - that->SdlViewport::PostConstructor(); - return that; - } -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/Sdl/SdlViewport.h --- a/Applications/Platforms/Sdl/SdlViewport.h Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,204 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - -#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 - -#include "SdlOpenGLContext.h" -#include "../../../OrthancStone/Sources/Scene2D/CairoCompositor.h" -#include "../../../OrthancStone/Sources/Viewport/IViewport.h" - -#if ORTHANC_ENABLE_OPENGL == 1 -# include "../../../OrthancStone/Sources/Scene2D/OpenGLCompositor.h" -#endif - -#include - -// TODO: required for UndoStack injection -// I don't like it either :) -#include - -#include -#include - -namespace OrthancStone -{ - class UndoStack; - - class SdlViewport : public IViewport, - public boost::enable_shared_from_this - { - private: - boost::recursive_mutex mutex_; - uint32_t refreshEvent_; - boost::shared_ptr controller_; - std::unique_ptr compositor_; - - void SendRefreshEvent(); - - protected: - class SdlLock : public ILock - { - private: - SdlViewport& that_; - boost::recursive_mutex::scoped_lock lock_; - - public: - explicit SdlLock(SdlViewport& that) : - that_(that), - lock_(that.mutex_) - { - } - - virtual bool HasCompositor() const ORTHANC_OVERRIDE - { - return true; - } - - virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE; - - virtual ViewportController& GetController() ORTHANC_OVERRIDE - { - return *that_.controller_; - } - - virtual void Invalidate() ORTHANC_OVERRIDE - { - that_.SendRefreshEvent(); - } - - virtual void RefreshCanvasSize() ORTHANC_OVERRIDE - { - that_.RefreshCanvasSize(); - } - }; - - void ClearCompositor() - { - compositor_.reset(); - } - - void AcquireCompositor(ICompositor* compositor /* takes ownership */); - - virtual void RefreshCanvasSize() = 0; - - protected: - SdlViewport(); - - void PostConstructor(); - - public: - bool IsRefreshEvent(const SDL_Event& event) const - { - return (event.type == refreshEvent_); - } - - virtual ILock* Lock() ORTHANC_OVERRIDE - { - return new SdlLock(*this); - } - - virtual uint32_t GetSdlWindowId() = 0; - - void UpdateSize(unsigned int width, - unsigned int height); - - virtual void ToggleMaximize() = 0; - - // Must be invoked from the main SDL thread - virtual void Paint() = 0; - }; - - -#if ORTHANC_ENABLE_OPENGL == 1 - class SdlOpenGLViewport : public SdlViewport - { - private: - SdlOpenGLContext context_; - - SdlOpenGLViewport(const std::string& title, - unsigned int width, - unsigned int height, - bool allowDpiScaling = true); - - protected: - virtual void RefreshCanvasSize() ORTHANC_OVERRIDE; - - public: - static boost::shared_ptr Create(const std::string& title, - unsigned int width, - unsigned int height, - bool allowDpiScaling = true); - - - virtual ~SdlOpenGLViewport(); - - virtual uint32_t GetSdlWindowId() ORTHANC_OVERRIDE; - - virtual void Paint() ORTHANC_OVERRIDE; - - virtual void ToggleMaximize() ORTHANC_OVERRIDE; - }; -#endif - - - class SdlCairoViewport : public SdlViewport - { - private: - SdlWindow window_; - SDL_Surface* sdlSurface_; - - void CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor); - - SdlCairoViewport(const std::string& title, - unsigned int width, - unsigned int height, - bool allowDpiScaling); - - protected: - virtual void RefreshCanvasSize() ORTHANC_OVERRIDE; - - public: - static boost::shared_ptr Create(const std::string& title, - unsigned int width, - unsigned int height, - bool allowDpiScaling = true); - - virtual ~SdlCairoViewport(); - - virtual uint32_t GetSdlWindowId() ORTHANC_OVERRIDE; - - virtual void Paint() ORTHANC_OVERRIDE; - - virtual void ToggleMaximize() ORTHANC_OVERRIDE; - }; -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/Sdl/SdlWindow.cpp --- a/Applications/Platforms/Sdl/SdlWindow.cpp Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,211 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#include "SdlWindow.h" - -#if ORTHANC_ENABLE_SDL == 1 - -#include -#include - -#ifdef WIN32 -#include // for SetProcessDpiAware -#endif -// WIN32 - -#include -#include -#include - -namespace OrthancStone -{ - SdlWindow::SdlWindow(const std::string& title, - unsigned int width, - unsigned int height, - bool enableOpenGl, - bool allowDpiScaling) : - maximized_(false) - { - // TODO Understand why, with SDL_WINDOW_OPENGL + MinGW32 + Release - // build mode, the application crashes whenever the SDL window is - // resized or maximized - - uint32_t windowFlags, rendererFlags; - if (enableOpenGl) - { - windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; - rendererFlags = SDL_RENDERER_ACCELERATED; - } - else - { - windowFlags = SDL_WINDOW_RESIZABLE; - rendererFlags = SDL_RENDERER_SOFTWARE; - } - -// TODO: probably required on MacOS X, too -#if defined(WIN32) && (_WIN32_WINNT >= 0x0600) - if (!allowDpiScaling) - { - // if we do NOT allow DPI scaling, it means an SDL pixel will be a real - // monitor pixel. This is needed for high-DPI applications - - // Enable high-DPI support on Windows - - // THE FOLLOWING HAS BEEN COMMENTED OUT BECAUSE IT WILL CRASH UNDER - // OLD WINDOWS VERSIONS - // ADD THIS AT THE TOP TO ENABLE IT: - // - //#pragma comment(lib, "Shcore.lib") THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness - //#include - //#include THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness - //#include THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness - // SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); - - // This is supported on Vista+ - SetProcessDPIAware(); - - windowFlags |= SDL_WINDOW_ALLOW_HIGHDPI; - } -#endif -// WIN32 - - window_ = SDL_CreateWindow(title.c_str(), - SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, - width, height, windowFlags); - - if (window_ == NULL) - { - LOG(ERROR) << "Cannot create the SDL window: " << SDL_GetError(); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - renderer_ = SDL_CreateRenderer(window_, -1, rendererFlags); - if (!renderer_) - { - LOG(ERROR) << "Cannot create the SDL renderer: " << SDL_GetError(); - SDL_DestroyWindow(window_); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - SdlWindow::~SdlWindow() - { - if (renderer_ != NULL) - { - SDL_DestroyRenderer(renderer_); - } - - if (window_ != NULL) - { - SDL_DestroyWindow(window_); - } - } - - - unsigned int SdlWindow::GetWidth() const - { - int w = -1; - SDL_GetWindowSize(window_, &w, NULL); - - if (w < 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - else - { - return static_cast(w); - } - } - - - unsigned int SdlWindow::GetHeight() const - { - int h = -1; - SDL_GetWindowSize(window_, NULL, &h); - - if (h < 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - else - { - return static_cast(h); - } - } - - - void SdlWindow::Render(SDL_Surface* surface) - { - /** - * "You are strongly encouraged to call SDL_RenderClear() to - * initialize the backbuffer before starting each new frame's - * drawing, even if you plan to overwrite every pixel." - * https://wiki.libsdl.org/SDL_RenderPresent - **/ - SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255); - SDL_RenderClear(renderer_); // Clear the entire screen to our selected color - - SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer_, surface); - if (texture != NULL) - { - SDL_RenderCopy(renderer_, texture, NULL, NULL); - SDL_DestroyTexture(texture); - } - - SDL_RenderPresent(renderer_); - } - - - void SdlWindow::ToggleMaximize() - { - if (maximized_) - { - SDL_RestoreWindow(window_); - maximized_ = false; - } - else - { - SDL_MaximizeWindow(window_); - maximized_ = true; - } - } - - - void SdlWindow::GlobalInitialize() - { - if (SDL_Init(SDL_INIT_VIDEO) != 0) - { - LOG(ERROR) << "Cannot initialize SDL"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - void SdlWindow::GlobalFinalize() - { - SDL_Quit(); - } -} - -#endif diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/Sdl/SdlWindow.h --- a/Applications/Platforms/Sdl/SdlWindow.h Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#pragma once - -#if ORTHANC_ENABLE_SDL == 1 - -#include -#include - -// Forward declaration of SDL type to avoid clashes with DCMTK headers -// on "typedef Sint8", in "StoneInitialization.cpp" -struct SDL_Window; -struct SDL_Renderer; -struct SDL_Surface; - -namespace OrthancStone -{ - class SdlWindow : public boost::noncopyable - { - private: - struct SDL_Window *window_; - struct SDL_Renderer *renderer_; - bool maximized_; - - public: - SdlWindow(const std::string& title, - unsigned int width, - unsigned int height, - bool enableOpenGl, - bool allowDpiScaling = true); - - ~SdlWindow(); - - SDL_Window *GetObject() const - { - return window_; - } - - unsigned int GetWidth() const; - - unsigned int GetHeight() const; - - /** - * WARNING: "Refresh()" cannot only be called from the main SDL - * thread, in which the window was created. Otherwise, the - * renderer displays nothing! - **/ - void Render(struct SDL_Surface* surface); - - void ToggleMaximize(); - - static void GlobalInitialize(); - - static void GlobalFinalize(); - }; -} - -#endif diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/OrthancStoneWebAssemblyConfiguration.cmake --- a/Applications/Platforms/WebAssembly/OrthancStoneWebAssemblyConfiguration.cmake Wed Jan 26 19:42:04 2022 +0100 +++ /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-2022 Osimis S.A., Belgium -# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - - - -##################################################################### -## Sanity check of the configuration -##################################################################### - -set(ENABLE_WEB_CLIENT OFF) -include(${ORTHANC_STONE_ROOT}/../Resources/CMake/OrthancStoneConfiguration.cmake) - -if (NOT ORTHANC_SANDBOXED) - message(FATAL_ERROR "WebAssembly target must me configured as sandboxed") -endif() - -if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten") - message(FATAL_ERROR "WebAssembly target requires the emscripten compiler") -endif() - -add_definitions( - -DORTHANC_ENABLE_SDL=0 - -DORTHANC_ENABLE_WASM=1 - ) - - -##################################################################### -## Additional source files for WebAssembly -##################################################################### - -list(APPEND ORTHANC_STONE_SOURCES - ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyCairoViewport.cpp - ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyLoadersContext.cpp - ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyOracle.cpp - ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyViewport.cpp - ) - -if (ENABLE_OPENGL) - list(APPEND ORTHANC_STONE_SOURCES - ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyOpenGLContext.cpp - ${CMAKE_CURRENT_LIST_DIR}/WebGLViewport.cpp - ${CMAKE_CURRENT_LIST_DIR}/WebGLViewportsRegistry.cpp - ) -endif() diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/OrthancStoneWebAssemblyParameters.cmake --- a/Applications/Platforms/WebAssembly/OrthancStoneWebAssemblyParameters.cmake Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -# Stone of Orthanc -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2022 Osimis S.A., Belgium -# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - - - -##################################################################### -## Load generic parameters -##################################################################### - -include(${CMAKE_CURRENT_LIST_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake) - - -##################################################################### -## CMake parameters tunable by the user -##################################################################### - -# None for now, but might contain stuff like memory settings - - -##################################################################### -## Internal CMake parameters to enable the optional subcomponents of -## the Stone of Orthanc -##################################################################### - -set(ENABLE_THREADS OFF CACHE INTERNAL "") diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/SharedLibrary/CMakeLists.txt --- a/Applications/Platforms/WebAssembly/SharedLibrary/CMakeLists.txt Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,211 +0,0 @@ -# Stone of Orthanc -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2022 Osimis S.A., Belgium -# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - - -cmake_minimum_required(VERSION 2.8.3) -cmake_policy(SET CMP0058 NEW) - -project(OrthancStoneModule) - - -# Warning message related to WebAssembly modules: We know that 1.38.41 -# DOES NOT work, but that 1.39.17 works. -message("") -message("=== IMPORTANT: Make sure to use a recent version of Emscripten (preferably >= 2.0.0) ===") -message("") - - -set(ORTHANC_STONE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/../../../../wasm-binaries/OrthancStoneModule" CACHE PATH "Where to put the WebAssembly binaries") - - -# Ask for the generation of a side module -set(WASM_FLAGS "-s SIDE_MODULE=1 -s EXPORT_ALL=1") # Must be before "Compiler.cmake" - - -# Configuration of the Emscripten compiler for WebAssembly target -# --------------------------------------------------------------- - -set(USE_WASM ON CACHE BOOL "") - -set(WASM_FLAGS "${WASM_FLAGS} -s WASM=1 -s FETCH=1") -if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") -endif() - -set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") -set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") -add_definitions( - -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 -) - - -# Stone of Orthanc configuration -# --------------------------------------------------------------- - -include(${CMAKE_SOURCE_DIR}/../OrthancStoneWebAssemblyParameters.cmake) - -SET(ENABLE_DCMTK ON) -SET(ENABLE_DCMTK_NETWORKING OFF) -SET(ENABLE_DCMTK_TRANSCODING OFF) -SET(ENABLE_GOOGLE_TEST OFF) -SET(ENABLE_LOCALE ON) # Necessary for text rendering -SET(ORTHANC_SANDBOXED ON) - -include(${CMAKE_SOURCE_DIR}/../OrthancStoneWebAssemblyConfiguration.cmake) - - - - -################################################################################ - -# The source files that register a callback cannot be part of a side -# module, and must be compiled in the main module. The following -# command can be used to identify such files: -# $ grep -lrE 'emscripten_' ../../Sources/ - -set(SOURCES_WITH_EMSCRIPTEN_CALLBACKS - ${CMAKE_SOURCE_DIR}/../WebAssemblyOracle.cpp - ${CMAKE_SOURCE_DIR}/../WebAssemblyViewport.cpp - ${CMAKE_SOURCE_DIR}/../WebAssemblyCairoViewport.cpp - ) - -list(REMOVE_ITEM ORTHANC_STONE_SOURCES - ${SOURCES_WITH_EMSCRIPTEN_CALLBACKS} - ) - -configure_file( - ${ORTHANC_STONE_ROOT}/../SharedLibrary/OrthancStone.h.in - ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone/OrthancStone.h - ) - -configure_file( - ${ORTHANC_FRAMEWORK_ROOT}/../SharedLibrary/OrthancFramework.h.in - ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework/OrthancFramework.h - ) - -file( - COPY ${CMAKE_SOURCE_DIR}/../../../../OrthancStone/Sources/ - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone - NO_SOURCE_PERMISSIONS - FILES_MATCHING - PATTERN "*.h" - PATTERN OrthancStone.h EXCLUDE - PATTERN "Deprecated*" EXCLUDE - ) - -file( - COPY ${ORTHANC_FRAMEWORK_ROOT}/ - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework - NO_SOURCE_PERMISSIONS - FILES_MATCHING - PATTERN "*.h" - PATTERN OrthancFramework.h EXCLUDE - ) - -add_executable(OrthancStoneModule - ${ORTHANC_STONE_SOURCES} - ${AUTOGENERATED_SOURCES} - ${CAIRO_SOURCES} - ${PIXMAN_SOURCES} - ${FREETYPE_SOURCES} - ) - -set_target_properties(OrthancStoneModule - PROPERTIES - COMPILE_FLAGS "${WASM_FLAGS}" - LINK_FLAGS "${WASM_LINKER_FLAGS}" - ) - -# CMake does not natively handle SIDE_MODULE, and believes that -# Emscripten produces a ".js" file (whereas it creates only the -# ".wasm"). Create a dummy ".js" for target to work. -add_custom_command( - TARGET OrthancStoneModule POST_BUILD - COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/OrthancStoneModule.js - ) - -file( - COPY ${BOOST_SOURCES_DIR}/boost/ - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/boost/ - NO_SOURCE_PERMISSIONS - FILES_MATCHING - PATTERN "*.h" - PATTERN "*.hpp" - PATTERN "*.ipp" - ) - -file( - COPY ${JSONCPP_SOURCES_DIR}/include/json/ - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/json/ - NO_SOURCE_PERMISSIONS - FILES_MATCHING - PATTERN "*.h" - ) - -file( - COPY ${CAIRO_SOURCES_DIR}/src/ - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/cairo/ - NO_SOURCE_PERMISSIONS - FILES_MATCHING - PATTERN "*.h" - ) - -set(DCMTK_MODULES - dcmdata - config - ofstd - oflog - ) - -foreach (module IN LISTS DCMTK_MODULES) - file( - COPY ${DCMTK_SOURCES_DIR}/ofstd/include/dcmtk/${module}/ - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk/${module}/ - NO_SOURCE_PERMISSIONS - FILES_MATCHING - PATTERN "*.h" - ) -endforeach() - - -install( - TARGETS OrthancStoneModule - DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/lib - ) - -install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/OrthancStoneModule.wasm - DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/lib - ) - -install( - DIRECTORY - ${CMAKE_CURRENT_BINARY_DIR}/Include/boost - ${CMAKE_CURRENT_BINARY_DIR}/Include/cairo - ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk - ${CMAKE_CURRENT_BINARY_DIR}/Include/json - ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework - ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone - DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/include/ - ) - -install(FILES - ${SOURCES_WITH_EMSCRIPTEN_CALLBACKS} - DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/src/orthanc-stone - ) diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/SharedLibrary/NOTES.txt --- a/Applications/Platforms/WebAssembly/SharedLibrary/NOTES.txt Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ - -Install Emscripten: -https://emscripten.org/docs/getting_started/downloads.html - -# cd ~/Downloads -# git clone https://github.com/emscripten-core/emsdk.git -# cd emsdk -# ./emsdk install 2.0.0 -# ./emsdk activate 2.0.0 - - -Then, if the installation path was "~/Downloads/emsdk/": - -# source ~/Downloads/emsdk/emsdk_env.sh -# cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja -# ninja install - -=> The binaries will be put in "../../../../wasm-binaries/" diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/UnitTests/CMakeLists.txt --- a/Applications/Platforms/WebAssembly/UnitTests/CMakeLists.txt Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,137 +0,0 @@ -# Stone of Orthanc -# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics -# Department, University Hospital of Liege, Belgium -# Copyright (C) 2017-2022 Osimis S.A., Belgium -# Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - - -cmake_minimum_required(VERSION 2.8.3) -cmake_policy(SET CMP0058 NEW) - -project(OrthancStone) - -set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") -set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") -set(ORTHANC_STONE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/../../../../wasm-binaries/" CACHE PATH "Where to put the WebAssembly binaries") - - -# Configuration of the Emscripten compiler for WebAssembly target -# --------------------------------------------------------------- -set(USE_WASM ON CACHE BOOL "") - -set(WASM_FLAGS "-s WASM=1 -s FETCH=1 -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") -if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") -endif() - -set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") -set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") -set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456") # 256MB + resize -set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") -add_definitions( - -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 -) - - -# Stone of Orthanc configuration -# --------------------------------------------------------------- -set(ALLOW_DOWNLOADS ON) - -include(${CMAKE_SOURCE_DIR}/../OrthancStoneWebAssemblyParameters.cmake) - -SET(ENABLE_DCMTK ON) # Necessary for the tests of the Orthanc framework -SET(ENABLE_PUGIXML ON) # Necessary for the tests of the Orthanc framework -SET(ENABLE_GOOGLE_TEST OFF) -SET(ENABLE_LOCALE ON) # Necessary for text rendering -SET(ENABLE_LUA ON) # Necessary for the tests of the Orthanc framework -SET(ORTHANC_SANDBOXED ON) - -# this will set up the build system for Stone of Orthanc and will -# populate the ORTHANC_STONE_SOURCES CMake variable -include(${CMAKE_SOURCE_DIR}/../OrthancStoneWebAssemblyConfiguration.cmake) - - -################################################################################ - -# Define the WASM module -# --------------------------------------------------------------- - -set(USE_SYSTEM_GOOGLE_TEST OFF CACHE BOOL "Use the system version of Google Test") -set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") -mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) -include(${ORTHANC_STONE_ROOT}/../Resources/Orthanc/CMake/DownloadPackage.cmake) -include(${ORTHANC_STONE_ROOT}/../Resources/Orthanc/CMake/GoogleTestConfiguration.cmake) - - -include(${CMAKE_SOURCE_DIR}/../../../../OrthancStone/UnitTestsSources/UnitTestsSources.cmake) - -EmbedResources( - RT_STRUCT_00 ${CMAKE_SOURCE_DIR}/../../../../OrthancStone/UnitTestsSources/TestStructureSet.json - ) - -add_executable(UnitTests - # Testing Stone - ${UNIT_TESTS_SOURCES} - - # Testing the Orthanc Framework - ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/DicomMapTests.cpp - ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/FrameworkTests.cpp - ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/FromDcmtkTests.cpp - ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/ImageProcessingTests.cpp - ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/ImageTests.cpp - ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/JpegLosslessTests.cpp - ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/LoggingTests.cpp - ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/LuaTests.cpp - ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/RestApiTests.cpp - ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/StreamTests.cpp - ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/ToolboxTests.cpp - - # Tests that are not compatible with WebAssembly - - # ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/FileStorageTests.cpp - # ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/JobsTests.cpp - # ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/MemoryCacheTests.cpp - # ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/SQLiteChromiumTests.cpp - # ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/SQLiteTests.cpp - # ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/ZipTests.cpp - - # Shared files - ${AUTOGENERATED_SOURCES} - ${BOOST_EXTENDED_SOURCES} - ${GOOGLE_TEST_SOURCES} - ${ORTHANC_STONE_SOURCES} - ) - - -# Declare installation files for the module -# --------------------------------------------------------------- -install( - TARGETS UnitTests - RUNTIME DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/UnitTests/ - ) - -# Declare installation files for the companion files (web scaffolding) -# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js -# (the generated JS loader for the WASM module) is handled by the `install1` -# section above: it is considered to be the binary output of -# the linker. -# --------------------------------------------------------------- -install( - FILES - ${CMAKE_SOURCE_DIR}/index.html - ${CMAKE_CURRENT_BINARY_DIR}/UnitTests.wasm - DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/UnitTests - ) diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/UnitTests/NOTES.txt --- a/Applications/Platforms/WebAssembly/UnitTests/NOTES.txt Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,18 +0,0 @@ -Native compilation (without Docker) -=================================== - -Install Emscripten: -https://emscripten.org/docs/getting_started/downloads.html - -Then, if the installation path is "~/Downloads/emsdk/": - -# source ~/Downloads/emsdk/emsdk_env.sh -# mkdir Build && cd Build -# cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja -# ninja install - -=> The binaries will be put in "../../../../../wasm-binaries/UnitTests/" - -# cd `pwd`/../../../../../wasm-binaries/UnitTests -# python3 -m http.server 8000 -# firefox http://localhost:8000/index.html diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/UnitTests/index.html --- a/Applications/Platforms/WebAssembly/UnitTests/index.html Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ - - - - - Stone of Orthanc - - -

Stone of Orthanc - Unit tests

-
-      Running the tests...
-    
- - - diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebAssemblyCairoViewport.cpp --- a/Applications/Platforms/WebAssembly/WebAssemblyCairoViewport.cpp Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1 -# include "WebAssemblyCairoViewport.h" -# include "../../../OrthancStone/Sources/Scene2D/CairoCompositor.h" -# include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" -#else -// This is the case when using the WebAssembly side module, and this -// source file must be compiled within the WebAssembly main module -# include -# include -# include -#endif - - -#include - -namespace OrthancStone -{ - void WebAssemblyCairoViewport::Paint(ICompositor& compositor, - ViewportController& controller) - { - compositor.Refresh(controller.GetScene()); - - // Create a temporary memory buffer for the canvas in JavaScript - Orthanc::ImageAccessor cairo; - dynamic_cast(compositor).GetCanvas().GetReadOnlyAccessor(cairo); - - const unsigned int width = cairo.GetWidth(); - const unsigned int height = cairo.GetHeight(); - - if (javascript_.get() == NULL || - javascript_->GetWidth() != width || - javascript_->GetHeight() != height) - { - javascript_.reset(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, width, height, - true /* force minimal pitch */)); - } - - // Convert from BGRA32 memory layout (only color mode supported - // by Cairo, which corresponds to CAIRO_FORMAT_ARGB32) to RGBA32 - // (as expected by HTML5 canvas). This simply amounts to - // swapping the B and R channels. Alpha channel is also set to - // full opacity (255). - uint8_t* q = reinterpret_cast(javascript_->GetBuffer()); - for (unsigned int y = 0; y < height; y++) - { - const uint8_t* p = reinterpret_cast(cairo.GetConstRow(y)); - for (unsigned int x = 0; x < width; x++) - { - q[0] = p[2]; // R - q[1] = p[1]; // G - q[2] = p[0]; // B - q[3] = 255; // A - - p += 4; - q += 4; - } - } - - if (width != 0 && - height != 0) - { - // Execute JavaScript commands to blit the image buffer onto the - // 2D drawing context of the HTML5 canvas - EM_ASM({ - const data = new Uint8ClampedArray(Module.HEAP8.buffer, $1, 4 * $2 * $3); - const img = new ImageData(data, $2, $3); - const ctx = document.getElementById(UTF8ToString($0)).getContext('2d'); - ctx.putImageData(img, 0, 0); - }, - GetCanvasId().c_str(), // $0 - javascript_->GetBuffer(), // $1 - javascript_->GetWidth(), // $2 - javascript_->GetHeight()); // $3 - } - } - - - WebAssemblyCairoViewport::WebAssemblyCairoViewport(const std::string& canvasId, - bool enableEmscriptenMouseEvents) : - WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents) - { - RefreshCanvasSize(); - AcquireCompositor(new CairoCompositor(GetCanvasWidth(), GetCanvasHeight())); - } - - - boost::shared_ptr WebAssemblyCairoViewport::Create( - const std::string& canvasId, bool enableEmscriptenMouseEvents) - { - boost::shared_ptr that = boost::shared_ptr( - new WebAssemblyCairoViewport(canvasId, enableEmscriptenMouseEvents)); - - that->WebAssemblyViewport::PostConstructor(); - return that; - } -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebAssemblyCairoViewport.h --- a/Applications/Platforms/WebAssembly/WebAssemblyCairoViewport.h Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#pragma once - -#include "WebAssemblyViewport.h" - -namespace OrthancStone -{ - class WebAssemblyCairoViewport : public WebAssemblyViewport - { - private: - std::unique_ptr javascript_; - - WebAssemblyCairoViewport(const std::string& canvasId, - bool enableEmscriptenMouseEvents); - - protected: - virtual void Paint(ICompositor& compositor, - ViewportController& controller) ORTHANC_OVERRIDE; - - public: - static boost::shared_ptr Create(const std::string& canvasId, - bool enableEmscriptenMouseEvents = true); - - virtual ~WebAssemblyCairoViewport() - { - ClearCompositor(); - } - }; -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebAssemblyLoadersContext.cpp --- a/Applications/Platforms/WebAssembly/WebAssemblyLoadersContext.cpp Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#include "WebAssemblyLoadersContext.h" - -namespace OrthancStone -{ - class WebAssemblyLoadersContext::Locker : public ILoadersContext::ILock - { - private: - WebAssemblyLoadersContext& that_; - - public: - explicit Locker(WebAssemblyLoadersContext& that) : - that_(that) - { - } - - virtual ILoadersContext& GetContext() const ORTHANC_OVERRIDE - { - return that_; - } - - virtual IObservable& GetOracleObservable() const ORTHANC_OVERRIDE - { - return that_.oracle_.GetOracleObservable(); - } - - virtual void Schedule(boost::shared_ptr receiver, - int priority, - IOracleCommand* command /* Takes ownership */) ORTHANC_OVERRIDE - { - that_.scheduler_->Schedule(receiver, priority, command); - } - - virtual void CancelRequests(boost::shared_ptr receiver) ORTHANC_OVERRIDE - { - that_.scheduler_->CancelRequests(receiver); - } - - virtual void CancelAllRequests() ORTHANC_OVERRIDE - { - that_.scheduler_->CancelAllRequests(); - } - - virtual void AddLoader(boost::shared_ptr loader) ORTHANC_OVERRIDE - { - that_.loaders_.push_back(loader); - } - - virtual void GetStatistics(uint64_t& scheduledCommands, - uint64_t& processedCommands) ORTHANC_OVERRIDE - { - scheduledCommands = that_.scheduler_->GetTotalScheduled(); - processedCommands = that_.scheduler_->GetTotalProcessed(); - } - }; - - - WebAssemblyLoadersContext::WebAssemblyLoadersContext(unsigned int maxHighPriority, - unsigned int maxStandardPriority, - unsigned int maxLowPriority) - { - oracle_.GetOracleObservable(); - scheduler_ = OracleScheduler::Create(oracle_, oracle_.GetOracleObservable(), oracle_, - maxHighPriority, maxStandardPriority, maxLowPriority); - - if (!scheduler_) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - } - - - ILoadersContext::ILock* WebAssemblyLoadersContext::Lock() - { - return new Locker(*this); - } -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebAssemblyLoadersContext.h --- a/Applications/Platforms/WebAssembly/WebAssemblyLoadersContext.h Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,69 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#pragma once - -#include "../../../OrthancStone/Sources/Loaders/ILoadersContext.h" -#include "../../../OrthancStone/Sources/Loaders/OracleScheduler.h" -#include "WebAssemblyOracle.h" - -#include - -namespace OrthancStone -{ - class WebAssemblyLoadersContext : public ILoadersContext - { - private: - class Locker; - - WebAssemblyOracle oracle_; - boost::shared_ptr scheduler_; - std::list< boost::shared_ptr > loaders_; - - public: - WebAssemblyLoadersContext(unsigned int maxHighPriority, - unsigned int maxStandardPriority, - unsigned int maxLowPriority); - - void SetLocalOrthanc(const std::string& root) - { - oracle_.SetLocalOrthanc(root); - } - - void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc) - { - oracle_.SetRemoteOrthanc(orthanc); - } - - void SetDicomCacheSize(size_t size) - { - oracle_.SetDicomCacheSize(size); - } - - WebAssemblyOracle::CachedInstanceAccessor* AccessCachedInstance(const std::string& sopInstanceUid) - { - return new WebAssemblyOracle::CachedInstanceAccessor(oracle_, sopInstanceUid); - } - - virtual ILock* Lock() ORTHANC_OVERRIDE; - }; -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebAssemblyOpenGLContext.cpp --- a/Applications/Platforms/WebAssembly/WebAssemblyOpenGLContext.cpp Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,201 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#include "WebAssemblyOpenGLContext.h" - -#include "../../../OrthancStone/Sources/StoneException.h" - -#include -#include - -#include -#include - -#include - -namespace OrthancStone -{ - namespace OpenGL - { - class WebAssemblyOpenGLContext::PImpl - { - private: - std::string canvasSelector_; - EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context_; - bool isContextLost_; - - public: - explicit PImpl(const std::string& canvasSelector) : - canvasSelector_(canvasSelector), - isContextLost_(false) - { - // Context configuration - EmscriptenWebGLContextAttributes attr; - emscripten_webgl_init_context_attributes(&attr); - - // The next line might be necessary to print using - // WebGL. Sometimes, if set to "false" (the default value), - // the canvas was rendered as a fully white or black - // area. UNCONFIRMED. - attr.preserveDrawingBuffer = true; - - context_ = emscripten_webgl_create_context(canvasSelector.c_str(), &attr); - if (context_ == 0) - { - std::string message("Cannot create an OpenGL context for the element with the following CSS selector: \""); - message += canvasSelector; - message += "\" Please make sure the -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 flag has been passed to Emscripten when building."; - LOG(ERROR) << message; - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, message); - } - } - - void* DebugGetInternalContext() const - { - return reinterpret_cast(context_); - } - - bool IsContextLost() - { - //LOG(TRACE) << "IsContextLost() for context " << std::hex << context_ << std::dec; - bool apiFlag = (emscripten_is_webgl_context_lost(context_) != 0); - isContextLost_ = apiFlag; - return isContextLost_; - } - - void SetLostContext() - { - isContextLost_ = true; - } - - ~PImpl() - { - try - { - EMSCRIPTEN_RESULT result = emscripten_webgl_destroy_context(context_); - if (result != EMSCRIPTEN_RESULT_SUCCESS) - { - LOG(ERROR) << "emscripten_webgl_destroy_context returned code " << result; - } - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::~PImpl: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::~PImpl: " << e.What(); - } - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in WebAssemblyOpenGLContext::~PImpl: " << e.what(); - } - catch (...) - { - LOG(ERROR) << "Unknown exception in WebAssemblyOpenGLContext::~PImpl"; - } - } - - const std::string& GetCanvasSelector() const - { - return canvasSelector_; - } - - void MakeCurrent() - { - if (IsContextLost()) - { - LOG(ERROR) << "MakeCurrent() called on lost context " << context_; - throw StoneException(ErrorCode_WebGLContextLost); - } - - if (emscripten_is_webgl_context_lost(context_)) - { - LOG(ERROR) << "OpenGL context has been lost for canvas selector: " << canvasSelector_; - SetLostContext(); - throw StoneException(ErrorCode_WebGLContextLost); - } - - if (emscripten_webgl_make_context_current(context_) != EMSCRIPTEN_RESULT_SUCCESS) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, - "Cannot set the OpenGL context"); - } - } - - void SwapBuffer() - { - /** - * "Rendered WebGL content is implicitly presented (displayed to - * the user) on the canvas when the event handler that renders with - * WebGL returns back to the browser event loop." - * https://emscripten.org/docs/api_reference/html5.h.html#webgl-context - * - * Could call "emscripten_webgl_commit_frame()" if - * "explicitSwapControl" option were set to "true". - **/ - } - }; - - - WebAssemblyOpenGLContext::WebAssemblyOpenGLContext(const std::string& canvasSelector) : - pimpl_(new PImpl(canvasSelector)) - { - } - - bool WebAssemblyOpenGLContext::IsContextLost() - { - return pimpl_->IsContextLost(); - } - - void WebAssemblyOpenGLContext::SetLostContext() - { - pimpl_->SetLostContext(); - } - - void* WebAssemblyOpenGLContext::DebugGetInternalContext() const - { - return pimpl_->DebugGetInternalContext(); - } - - void WebAssemblyOpenGLContext::MakeCurrent() - { - assert(pimpl_.get() != NULL); - pimpl_->MakeCurrent(); - } - - void WebAssemblyOpenGLContext::SwapBuffer() - { - assert(pimpl_.get() != NULL); - pimpl_->SwapBuffer(); - } - - const std::string& WebAssemblyOpenGLContext::GetCanvasSelector() const - { - assert(pimpl_.get() != NULL); - return pimpl_->GetCanvasSelector(); - } - } -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebAssemblyOpenGLContext.h --- a/Applications/Platforms/WebAssembly/WebAssemblyOpenGLContext.h Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#pragma once - -#if !defined(ORTHANC_ENABLE_WASM) -# error Macro ORTHANC_ENABLE_WASM must be defined -#endif - -#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 "../../../OrthancStone/Sources/OpenGL/IOpenGLContext.h" - -#include // For ORTHANC_OVERRIDE - -#include - -namespace OrthancStone -{ - namespace OpenGL - { - class WebAssemblyOpenGLContext : public OpenGL::IOpenGLContext - { - private: - class PImpl; - boost::shared_ptr pimpl_; - - public: - explicit WebAssemblyOpenGLContext(const std::string& canvasSelector); - - virtual bool IsContextLost() ORTHANC_OVERRIDE; - - virtual void MakeCurrent() ORTHANC_OVERRIDE; - - virtual void SwapBuffer() ORTHANC_OVERRIDE; - - /** - Returns true if the underlying context has been successfully recreated - */ - //bool TryRecreate(); - - const std::string& GetCanvasSelector() const; - - - /** - * This is for manual context loss (debug purposes) - **/ - void* DebugGetInternalContext() const; - void SetLostContext(); - }; - } -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebAssemblyOracle.cpp --- a/Applications/Platforms/WebAssembly/WebAssemblyOracle.cpp Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,875 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1 -# include "WebAssemblyOracle_Includes.h" -#else -// This is the case when using the WebAssembly side module, and this -// source file must be compiled within the WebAssembly main module -# include -#endif - -#include -#include - -#include -#include -#include - - -#if ORTHANC_ENABLE_DCMTK == 1 -static unsigned int BUCKET_SOP = 1; -#endif - - -namespace OrthancStone -{ - class WebAssemblyOracle::TimeoutContext - { - private: - WebAssemblyOracle& oracle_; - boost::weak_ptr receiver_; - std::unique_ptr command_; - - public: - TimeoutContext(WebAssemblyOracle& oracle, - boost::weak_ptr receiver, - IOracleCommand* command) : - oracle_(oracle), - receiver_(receiver) - { - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - command_.reset(dynamic_cast(command)); - } - } - - void EmitMessage() - { - assert(command_.get() != NULL); - - SleepOracleCommand::TimeoutMessage message(*command_); - oracle_.EmitMessage(receiver_, message); - } - - static void Callback(void *userData) - { - std::unique_ptr context(reinterpret_cast(userData)); - context->EmitMessage(); - } - }; - - - /** - This object is created on the heap for every http request. - It is deleted in the success (or error) callbacks. - - This object references the receiver of the request. Since this is a raw - reference, we need additional checks to make sure we send the response to - the same object, for the object can be deleted and a new one recreated at the - same address (it often happens in the [single-threaded] browser context). - */ - class WebAssemblyOracle::FetchContext : public boost::noncopyable - { - private: - WebAssemblyOracle& oracle_; - boost::weak_ptr receiver_; - std::unique_ptr command_; - std::string expectedContentType_; - - public: - FetchContext(WebAssemblyOracle& oracle, - boost::weak_ptr receiver, - IOracleCommand* command, - const std::string& expectedContentType) : - oracle_(oracle), - receiver_(receiver), - command_(command), - expectedContentType_(expectedContentType) - { - if (Orthanc::Logging::IsTraceLevelEnabled()) - { - // Calling "receiver.lock()" is expensive, hence the quick check if TRACE is enabled - LOG(TRACE) << "WebAssemblyOracle::FetchContext::FetchContext() | " - << "receiver address = " << std::hex << receiver.lock().get(); - } - - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - const std::string& GetExpectedContentType() const - { - return expectedContentType_; - } - - void EmitException(const Orthanc::OrthancException& exception) - { - assert(command_.get() != NULL); - OracleCommandExceptionMessage message(*command_, exception); - oracle_.EmitMessage(receiver_, message); - } - - void ProcessFetchResult(const std::string& answer, - const HttpHeaders& headers) - { - assert(command_.get() != NULL); - oracle_.ProcessFetchResult(receiver_, answer, headers, *command_); - } - - static void SuccessCallback(emscripten_fetch_t *fetch) - { - /** - * Firstly, make a local copy of the fetched information, and - * free data associated with the fetch. - **/ - - if (fetch->userData == NULL) - { - LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback fetch->userData is NULL!"; - return; - } - - std::unique_ptr context(reinterpret_cast(fetch->userData)); - - std::string answer; - if (fetch->numBytes > 0) - { - answer.assign(fetch->data, fetch->numBytes); - } - - - /** - * Retrieving the headers of the HTTP answer. - **/ - HttpHeaders headers; - -#if (__EMSCRIPTEN_major__ < 1 || \ - (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ < 38) || \ - (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ < 37)) -# warning Consider upgrading Emscripten to a version above 1.38.37, incomplete support of Fetch API - - /** - * HACK - If emscripten < 1.38.37, the fetch API does not - * contain a way to retrieve the HTTP headers of the answer. We - * make the assumption that the "Content-Type" header of the - * response is the same as the "Accept" header of the - * query. This is fixed thanks to the - * "emscripten_fetch_get_response_headers()" function that was - * added to "fetch.h" at emscripten-1.38.37 on 2019-06-26. - * - * https://github.com/emscripten-core/emscripten/blob/1.38.37/system/include/emscripten/fetch.h - * https://github.com/emscripten-core/emscripten/pull/8486 - **/ - if (fetch->userData != NULL) - { - if (!context->GetExpectedContentType().empty()) - { - headers["Content-Type"] = context->GetExpectedContentType(); - } - } -#else - { - size_t size = emscripten_fetch_get_response_headers_length(fetch); - - std::string plainHeaders(size + 1, '\0'); - emscripten_fetch_get_response_headers(fetch, &plainHeaders[0], size + 1); - - std::vector tokens; - Orthanc::Toolbox::TokenizeString(tokens, plainHeaders, '\n'); - - for (size_t i = 0; i < tokens.size(); i++) - { - size_t p = tokens[i].find(':'); - if (p != std::string::npos) - { - std::string key = Orthanc::Toolbox::StripSpaces(tokens[i].substr(0, p)); - std::string value = Orthanc::Toolbox::StripSpaces(tokens[i].substr(p + 1)); - headers[key] = value; - } - } - } -#endif - - LOG(TRACE) << "About to call emscripten_fetch_close"; - emscripten_fetch_close(fetch); - LOG(TRACE) << "Successfully called emscripten_fetch_close"; - - /** - * Secondly, use the retrieved data. - * IMPORTANT NOTE: the receiver might be dead. This is prevented - * by the object responsible for zombie check, later on. - **/ - try - { - if (context.get() == NULL) - { - LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback: (context.get() == NULL)"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - context->ProcessFetchResult(answer, headers); - } - } - catch (Orthanc::OrthancException& e) - { - LOG(INFO) << "Error while processing a fetch answer in the oracle: " << e.What(); - context->EmitException(e); - } - } - - static void FailureCallback(emscripten_fetch_t *fetch) - { - std::unique_ptr context(reinterpret_cast(fetch->userData)); - -#if 0 - { - const size_t kEmscriptenStatusTextSize = sizeof(emscripten_fetch_t::statusText); - char message[kEmscriptenStatusTextSize + 1]; - memcpy(message, fetch->statusText, kEmscriptenStatusTextSize); - message[kEmscriptenStatusTextSize] = 0; - - LOG(ERROR) << "Fetching " << fetch->url - << " failed, HTTP failure status code: " << fetch->status - << " | statusText = " << message - << " | numBytes = " << fetch->numBytes - << " | totalBytes = " << fetch->totalBytes - << " | readyState = " << fetch->readyState; - } -#endif - - context->EmitException(Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol)); - - /** - * TODO - The following code leads to an infinite recursion, at - * least with Firefox running on incognito mode => WHY? - **/ - emscripten_fetch_close(fetch); // Also free data on failure. - } - }; - - - - class WebAssemblyOracle::FetchCommand : public boost::noncopyable - { - private: - WebAssemblyOracle& oracle_; - boost::weak_ptr receiver_; - std::unique_ptr command_; - Orthanc::HttpMethod method_; - std::string url_; - std::string body_; - HttpHeaders headers_; - unsigned int timeout_; - std::string expectedContentType_; - bool hasCredentials_; - std::string username_; - std::string password_; - - public: - FetchCommand(WebAssemblyOracle& oracle, - boost::weak_ptr receiver, - IOracleCommand* command) : - oracle_(oracle), - receiver_(receiver), - command_(command), - method_(Orthanc::HttpMethod_Get), - timeout_(0), - hasCredentials_(false) - { - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - } - - void SetMethod(Orthanc::HttpMethod method) - { - method_ = method; - } - - Orthanc::HttpMethod GetMethod() const - { - return method_; - } - - void SetUrl(const std::string& url) - { - url_ = url; - } - - const std::string& GetUrl() const - { - return url_; - } - - void SetBody(std::string& body /* will be swapped */) - { - body_.swap(body); - } - - void AddHttpHeaders(const HttpHeaders& headers) - { - for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) - { - headers_[it->first] = it->second; - } - } - - const HttpHeaders& GetHttpHeaders() const - { - return headers_; - } - - void SetTimeout(unsigned int timeout) - { - timeout_ = timeout; - } - - unsigned int GetTimeout() const - { - return timeout_; - } - - void SetCredentials(const std::string& username, - const std::string& password) - { - hasCredentials_ = true; - username_ = username; - password_ = password; - } - - void Execute() - { - if (command_.get() == NULL) - { - // Cannot call Execute() twice - LOG(ERROR) << "WebAssemblyOracle::Execute(): (command_.get() == NULL)"; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - - emscripten_fetch_attr_t attr; - emscripten_fetch_attr_init(&attr); - - const char* method; - - switch (method_) - { - case Orthanc::HttpMethod_Get: - method = "GET"; - break; - - case Orthanc::HttpMethod_Post: - method = "POST"; - break; - - case Orthanc::HttpMethod_Delete: - method = "DELETE"; - break; - - case Orthanc::HttpMethod_Put: - method = "PUT"; - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - strcpy(attr.requestMethod, method); - - attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_REPLACE; - attr.onsuccess = FetchContext::SuccessCallback; - attr.onerror = FetchContext::FailureCallback; - attr.timeoutMSecs = timeout_ * 1000; - - if (hasCredentials_) - { - attr.withCredentials = EM_TRUE; - attr.userName = username_.c_str(); - attr.password = password_.c_str(); - } - - std::vector headers; - headers.reserve(2 * headers_.size() + 1); - - std::string expectedContentType; - - for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) - { - std::string key; - Orthanc::Toolbox::ToLowerCase(key, it->first); - - if (key == "accept") - { - expectedContentType = it->second; - } - - if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header - { - headers.push_back(it->first.c_str()); - headers.push_back(it->second.c_str()); - } - } - - headers.push_back(NULL); // Termination of the array of HTTP headers - - attr.requestHeaders = &headers[0]; - - char* requestData = NULL; - if (!body_.empty()) - requestData = reinterpret_cast(malloc(body_.size())); - - try - { - if (!body_.empty()) - { - memcpy(requestData, &(body_[0]), body_.size()); - attr.requestDataSize = body_.size(); - attr.requestData = requestData; - } - attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); - - // Must be the last call to prevent memory leak on error - emscripten_fetch(&attr, url_.c_str()); - } - catch(...) - { - if(requestData != NULL) - free(requestData); - throw; - } - } - }; - - - void WebAssemblyOracle::ProcessFetchResult(boost::weak_ptr& receiver, - const std::string& answer, - const HttpHeaders& headers, - const IOracleCommand& command) - { - switch (command.GetType()) - { - case IOracleCommand::Type_Http: - { - HttpCommand::SuccessMessage message(dynamic_cast(command), headers, answer); - EmitMessage(receiver, message); - break; - } - - case IOracleCommand::Type_OrthancRestApi: - { - LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call EmitMessage(message);"; - OrthancRestApiCommand::SuccessMessage message - (dynamic_cast(command), headers, answer); - EmitMessage(receiver, message); - break; - } - - case IOracleCommand::Type_GetOrthancImage: - { - dynamic_cast(command).ProcessHttpAnswer(receiver, *this, answer, headers); - break; - } - - case IOracleCommand::Type_GetOrthancWebViewerJpeg: - { - dynamic_cast(command).ProcessHttpAnswer(receiver, *this, answer); - break; - } - - case IOracleCommand::Type_ParseDicomFromWado: - { -#if ORTHANC_ENABLE_DCMTK == 1 - const ParseDicomFromWadoCommand& c = dynamic_cast(command); - - size_t fileSize; - std::unique_ptr dicom - (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers)); - - { - ParseDicomSuccessMessage message(c, c.GetSource(), *dicom, fileSize, true); - EmitMessage(receiver, message); - } - - if (dicomCache_.get()) - { - // Store it into the cache for future use - dicomCache_->Acquire(BUCKET_SOP, c.GetSopInstanceUid(), dicom.release(), fileSize, true); - } -#else - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); -#endif - break; - } - - default: - LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): " - << command.GetType(); - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - - void WebAssemblyOracle::SetOrthancUrl(FetchCommand& command, - const std::string& uri) const - { - if (isLocalOrthanc_) - { - command.SetUrl(localOrthancRoot_ + uri); - } - else - { - command.SetUrl(remoteOrthanc_.GetUrl() + uri); - command.AddHttpHeaders(remoteOrthanc_.GetHttpHeaders()); - - if (!remoteOrthanc_.GetUsername().empty()) - { - command.SetCredentials(remoteOrthanc_.GetUsername(), remoteOrthanc_.GetPassword()); - } - } - } - - - void WebAssemblyOracle::Execute(boost::weak_ptr receiver, - HttpCommand* command) - { - FetchCommand fetch(*this, receiver, command); - - fetch.SetMethod(command->GetMethod()); - fetch.SetUrl(command->GetUrl()); - fetch.AddHttpHeaders(command->GetHttpHeaders()); - fetch.SetTimeout(command->GetTimeout()); - - if (command->GetMethod() == Orthanc::HttpMethod_Post || - command->GetMethod() == Orthanc::HttpMethod_Put) - { - std::string body; - command->SwapBody(body); - fetch.SetBody(body); - } - - fetch.Execute(); - } - - - void WebAssemblyOracle::Execute(boost::weak_ptr receiver, - OrthancRestApiCommand* command) - { - try - { - //LOG(TRACE) << "*********** WebAssemblyOracle::Execute."; - //LOG(TRACE) << "WebAssemblyOracle::Execute | command = " << command; - FetchCommand fetch(*this, receiver, command); - - fetch.SetMethod(command->GetMethod()); - SetOrthancUrl(fetch, command->GetUri()); - fetch.AddHttpHeaders(command->GetHttpHeaders()); - fetch.SetTimeout(command->GetTimeout()); - - if (command->GetMethod() == Orthanc::HttpMethod_Post || - command->GetMethod() == Orthanc::HttpMethod_Put) - { - std::string body; - command->SwapBody(body); - fetch.SetBody(body); - } - - fetch.Execute(); - //LOG(TRACE) << "*********** successful end of WebAssemblyOracle::Execute."; - } - catch (const Orthanc::OrthancException& e) - { - if (e.HasDetails()) - { - LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What() << " Details: " << e.GetDetails(); - } - else - { - LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What(); - } - //LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute."; - throw; - } - catch (const std::exception& e) - { - LOG(ERROR) << "std::exception in WebAssemblyOracle::Execute: " << e.what(); -// LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute."; - throw; - } - catch (...) - { - LOG(ERROR) << "Unknown exception in WebAssemblyOracle::Execute"; -// LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute."; - throw; - } - } - - - void WebAssemblyOracle::Execute(boost::weak_ptr receiver, - GetOrthancImageCommand* command) - { - FetchCommand fetch(*this, receiver, command); - - SetOrthancUrl(fetch, command->GetUri()); - fetch.AddHttpHeaders(command->GetHttpHeaders()); - fetch.SetTimeout(command->GetTimeout()); - - fetch.Execute(); - } - - - void WebAssemblyOracle::Execute(boost::weak_ptr receiver, - GetOrthancWebViewerJpegCommand* command) - { - FetchCommand fetch(*this, receiver, command); - - SetOrthancUrl(fetch, command->GetUri()); - fetch.AddHttpHeaders(command->GetHttpHeaders()); - fetch.SetTimeout(command->GetTimeout()); - - fetch.Execute(); - } - - - void WebAssemblyOracle::Execute(boost::weak_ptr receiver, - ParseDicomFromWadoCommand* command) - { - std::unique_ptr protection(command); - -#if ORTHANC_ENABLE_DCMTK == 1 - if (dicomCache_.get()) - { - ParsedDicomCache::Reader reader(*dicomCache_, BUCKET_SOP, protection->GetSopInstanceUid()); - if (reader.IsValid() && - reader.HasPixelData()) - { - // Reuse the DICOM file from the cache - ParseDicomSuccessMessage message(*protection, protection->GetSource(), reader.GetDicom(), - reader.GetFileSize(), reader.HasPixelData()); - EmitMessage(receiver, message); - return; - } - } -#endif - - switch (command->GetRestCommand().GetType()) - { - case IOracleCommand::Type_Http: - { - const HttpCommand& rest = - dynamic_cast(protection->GetRestCommand()); - - FetchCommand fetch(*this, receiver, protection.release()); - - fetch.SetMethod(rest.GetMethod()); - fetch.SetUrl(rest.GetUrl()); - fetch.AddHttpHeaders(rest.GetHttpHeaders()); - fetch.SetTimeout(rest.GetTimeout()); - - if (rest.GetMethod() == Orthanc::HttpMethod_Post || - rest.GetMethod() == Orthanc::HttpMethod_Put) - { - std::string body = rest.GetBody(); - fetch.SetBody(body); - } - - fetch.Execute(); - break; - } - - case IOracleCommand::Type_OrthancRestApi: - { - const OrthancRestApiCommand& rest = - dynamic_cast(protection->GetRestCommand()); - - FetchCommand fetch(*this, receiver, protection.release()); - - fetch.SetMethod(rest.GetMethod()); - SetOrthancUrl(fetch, rest.GetUri()); - fetch.AddHttpHeaders(rest.GetHttpHeaders()); - fetch.SetTimeout(rest.GetTimeout()); - - if (rest.GetMethod() == Orthanc::HttpMethod_Post || - rest.GetMethod() == Orthanc::HttpMethod_Put) - { - std::string body = rest.GetBody(); - fetch.SetBody(body); - } - - fetch.Execute(); - break; - } - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - - - bool WebAssemblyOracle::Schedule(boost::shared_ptr receiver, - IOracleCommand* command) - { - LOG(TRACE) << "WebAssemblyOracle::Schedule : receiver = " - << std::hex << receiver.get(); - - std::unique_ptr protection(command); - - if (command == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - - switch (command->GetType()) - { - case IOracleCommand::Type_Http: - Execute(receiver, dynamic_cast(protection.release())); - break; - - case IOracleCommand::Type_OrthancRestApi: - Execute(receiver, dynamic_cast(protection.release())); - break; - - case IOracleCommand::Type_GetOrthancImage: - Execute(receiver, dynamic_cast(protection.release())); - break; - - case IOracleCommand::Type_GetOrthancWebViewerJpeg: - Execute(receiver, dynamic_cast(protection.release())); - break; - - case IOracleCommand::Type_Sleep: - { - unsigned int timeoutMS = dynamic_cast(command)->GetDelay(); - emscripten_set_timeout(TimeoutContext::Callback, timeoutMS, - new TimeoutContext(*this, receiver, protection.release())); - break; - } - - case IOracleCommand::Type_ParseDicomFromWado: -#if ORTHANC_ENABLE_DCMTK == 1 - Execute(receiver, dynamic_cast(protection.release())); -#else - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, - "DCMTK must be enabled to parse DICOM files"); -#endif - break; - - default: - LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in Schedule): " - << command->GetType(); - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - - return true; - } - - - void WebAssemblyOracle::SetDicomCacheSize(size_t size) - { -#if ORTHANC_ENABLE_DCMTK == 1 - if (size == 0) - { - dicomCache_.reset(); - } - else - { - dicomCache_.reset(new ParsedDicomCache(size)); - } -#else - LOG(INFO) << "DCMTK support is disabled, the DICOM cache is disabled"; -#endif - } - - - WebAssemblyOracle::CachedInstanceAccessor::CachedInstanceAccessor(WebAssemblyOracle& oracle, - const std::string& sopInstanceUid) - { -#if ORTHANC_ENABLE_DCMTK == 1 - if (oracle.dicomCache_.get() != NULL) - { - reader_.reset(new ParsedDicomCache::Reader(*oracle.dicomCache_, BUCKET_SOP, sopInstanceUid)); - } -#endif - } - - bool WebAssemblyOracle::CachedInstanceAccessor::IsValid() const - { -#if ORTHANC_ENABLE_DCMTK == 1 - return (reader_.get() != NULL && - reader_->IsValid()); -#else - return false; -#endif - } - -#if ORTHANC_ENABLE_DCMTK == 1 - const Orthanc::ParsedDicomFile& WebAssemblyOracle::CachedInstanceAccessor::GetDicom() const - { - if (IsValid()) - { - assert(reader_.get() != NULL); - return reader_->GetDicom(); - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } -#endif - - size_t WebAssemblyOracle::CachedInstanceAccessor::GetFileSize() const - { -#if ORTHANC_ENABLE_DCMTK == 1 - if (IsValid()) - { - assert(reader_.get() != NULL); - return reader_->GetFileSize(); - } - else -#endif - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - bool WebAssemblyOracle::CachedInstanceAccessor::HasPixelData() const - { -#if ORTHANC_ENABLE_DCMTK == 1 - if (IsValid()) - { - assert(reader_.get() != NULL); - return reader_->HasPixelData(); - } - else -#endif - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebAssemblyOracle.h --- a/Applications/Platforms/WebAssembly/WebAssemblyOracle.h Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,155 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#pragma once - -#include "../../../OrthancStone/Sources/OrthancStone.h" - -#if !defined(ORTHANC_ENABLE_WASM) -# error The macro ORTHANC_ENABLE_WASM must be defined -#endif - -#if ORTHANC_ENABLE_WASM != 1 -# error This file can only compiled for WebAssembly -#endif - -#include "../../../OrthancStone/Sources/Messages/IObservable.h" -#include "../../../OrthancStone/Sources/Messages/IMessageEmitter.h" -#include "../../../OrthancStone/Sources/Oracle/IOracle.h" - -#if ORTHANC_ENABLE_DCMTK == 1 -# include "../../../OrthancStone/Sources/Toolbox/ParsedDicomCache.h" -#endif - -#include // For ORTHANC_OVERRIDE -#include - -#include - -namespace OrthancStone -{ - class GetOrthancImageCommand; - class GetOrthancWebViewerJpegCommand; - class HttpCommand; - class OrthancRestApiCommand; - class ParseDicomFromWadoCommand; - - class WebAssemblyOracle : - public IOracle, - public IMessageEmitter - { - private: - typedef std::map HttpHeaders; - - class TimeoutContext; - class FetchContext; - class FetchCommand; - - void SetOrthancUrl(FetchCommand& command, - const std::string& uri) const; - - void Execute(boost::weak_ptr receiver, - HttpCommand* command); - - void Execute(boost::weak_ptr receiver, - OrthancRestApiCommand* command); - - void Execute(boost::weak_ptr receiver, - GetOrthancImageCommand* command); - - void Execute(boost::weak_ptr receiver, - GetOrthancWebViewerJpegCommand* command); - - void Execute(boost::weak_ptr receiver, - ParseDicomFromWadoCommand* command); - - IObservable oracleObservable_; - bool isLocalOrthanc_; - std::string localOrthancRoot_; - Orthanc::WebServiceParameters remoteOrthanc_; - -#if ORTHANC_ENABLE_DCMTK == 1 - std::unique_ptr dicomCache_; -#endif - - void ProcessFetchResult(boost::weak_ptr& receiver, - const std::string& answer, - const HttpHeaders& headers, - const IOracleCommand& command); - - public: - WebAssemblyOracle() : - isLocalOrthanc_(false) - { - } - - virtual void EmitMessage(boost::weak_ptr observer, - const IMessage& message) ORTHANC_OVERRIDE - { - oracleObservable_.EmitMessage(observer, message); - } - - virtual bool Schedule(boost::shared_ptr receiver, - IOracleCommand* command) ORTHANC_OVERRIDE; - - IObservable& GetOracleObservable() - { - return oracleObservable_; - } - - void SetLocalOrthanc(const std::string& root) - { - isLocalOrthanc_ = true; - localOrthancRoot_ = root; - } - - void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc) - { - isLocalOrthanc_ = false; - remoteOrthanc_ = orthanc; - } - - void SetDicomCacheSize(size_t size); - - class CachedInstanceAccessor : public boost::noncopyable - { - private: -#if ORTHANC_ENABLE_DCMTK == 1 - std::unique_ptr reader_; -#endif - - public: - CachedInstanceAccessor(WebAssemblyOracle& oracle, - const std::string& sopInstanceUid); - - bool IsValid() const; - -#if ORTHANC_ENABLE_DCMTK == 1 - const Orthanc::ParsedDicomFile& GetDicom() const; -#endif - - size_t GetFileSize() const; - - bool HasPixelData() const; - }; - }; -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebAssemblyOracle_Includes.h --- a/Applications/Platforms/WebAssembly/WebAssemblyOracle_Includes.h Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#pragma once - -/** - * This file serves as an indirection to avoid large "#if - * ORTHANC_BUILDING_STONE_LIBRARY == 1" in "WebAssemblyOracle.cpp" - **/ - -#include "WebAssemblyOracle.h" - -#include "../../../OrthancStone/Sources/Oracle/OracleCommandExceptionMessage.h" - -#if ORTHANC_ENABLE_DCMTK == 1 -# include "../../../OrthancStone/Sources/Oracle/ParseDicomSuccessMessage.h" -#endif - -#include "../../../OrthancStone/Sources/Oracle/GetOrthancImageCommand.h" -#include "../../../OrthancStone/Sources/Oracle/GetOrthancWebViewerJpegCommand.h" -#include "../../../OrthancStone/Sources/Oracle/HttpCommand.h" -#include "../../../OrthancStone/Sources/Oracle/OrthancRestApiCommand.h" -#include "../../../OrthancStone/Sources/Oracle/ParseDicomFromWadoCommand.h" -#include "../../../OrthancStone/Sources/Oracle/SleepOracleCommand.h" diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebAssemblyViewport.cpp --- a/Applications/Platforms/WebAssembly/WebAssemblyViewport.cpp Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,473 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1 -# include "WebAssemblyViewport.h" -# include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" -# include "../../../OrthancStone/Sources/Toolbox/GenericToolbox.h" -# include "../../../OrthancStone/Sources/Viewport/DefaultViewportInteractor.h" -#else -// This is the case when using the WebAssembly side module, and this -// source file must be compiled within the WebAssembly main module -# include -# include -# include -# include -#endif - - -#include - -#include -#include -#include - -namespace OrthancStone -{ - static void ConvertMouseEvent(PointerEvent& target, - const EmscriptenMouseEvent& source, - const ICompositor& compositor) - { - int x = static_cast(source.targetX); - int y = static_cast(source.targetY); - - switch (source.button) - { - case 0: - target.SetMouseButton(MouseButton_Left); - break; - - case 1: - target.SetMouseButton(MouseButton_Middle); - break; - - case 2: - target.SetMouseButton(MouseButton_Right); - break; - - default: - target.SetMouseButton(MouseButton_None); - break; - } - - target.AddPosition(compositor.GetPixelCenterCoordinates(x, y)); - target.SetAltModifier(source.altKey); - target.SetControlModifier(source.ctrlKey); - target.SetShiftModifier(source.shiftKey); - } - - - class WebAssemblyViewport::WasmLock : public ILock - { - private: - WebAssemblyViewport& that_; - - public: - WasmLock(WebAssemblyViewport& that) : - that_(that) - { - } - - virtual bool HasCompositor() const ORTHANC_OVERRIDE - { - return that_.compositor_.get() != NULL; - } - - virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE - { - if (that_.compositor_.get() == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - return *that_.compositor_; - } - } - - virtual ViewportController& GetController() ORTHANC_OVERRIDE - { - assert(that_.controller_); - return *that_.controller_; - } - - virtual void Invalidate() ORTHANC_OVERRIDE - { - that_.Invalidate(); - } - - virtual void RefreshCanvasSize() ORTHANC_OVERRIDE - { - that_.RefreshCanvasSize(); - } - }; - - EM_BOOL WebAssemblyViewport::OnRequestAnimationFrame(double time, void *userData) - { - LOG(TRACE) << __func__; - - WebAssemblyViewport* that = - WebAssemblyViewport::DereferenceObjectCookie(userData); - - if (that != NULL) - { - if (that->compositor_.get() != NULL && - that->controller_ /* should always be true */) - { - that->Paint(*that->compositor_, *that->controller_); - } - } - else - { - LOG(TRACE) << "WebAssemblyViewport::OnRequestAnimationFrame: the " << - "WebAssemblyViewport is deleted and Paint will not be called."; - } - WebAssemblyViewport::ReleaseObjectCookie(userData); - LOG(TRACE) << "Exiting: " << __func__; - return true; - } - - EM_BOOL WebAssemblyViewport::OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) - { - LOG(TRACE) << __func__; - WebAssemblyViewport* that = reinterpret_cast(userData); - - if (that->compositor_.get() != NULL) - { - that->RefreshCanvasSize(); - that->Invalidate(); - } - - LOG(TRACE) << "Exiting: " << __func__; - return true; - } - - - EM_BOOL WebAssemblyViewport::OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) - { - WebAssemblyViewport* that = reinterpret_cast(userData); - - LOG(TRACE) << "mouse down: " << that->GetCanvasCssSelector(); - - if (that->compositor_.get() != NULL && - that->interactor_.get() != NULL) - { - PointerEvent pointer; - ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); - - that->controller_->HandleMousePress(*that->interactor_, pointer, - that->compositor_->GetCanvasWidth(), - that->compositor_->GetCanvasHeight()); - that->Invalidate(); - } - - LOG(TRACE) << "Exiting: " << __func__; - return true; - } - - - EM_BOOL WebAssemblyViewport::OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) - { - WebAssemblyViewport* that = reinterpret_cast(userData); - - if (that->compositor_.get() != NULL) - { - if (that->controller_->HasActiveTracker()) - { - PointerEvent pointer; - ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); - - if (that->controller_->HandleMouseMove(pointer)) - { - that->Invalidate(); - } - } - else if (that->interactor_.get() != NULL && - that->interactor_->HasMouseHover()) - { - // New in Stone Web viewer 2.0 - PointerEvent pointer; - ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); - that->interactor_->HandleMouseHover(*that, pointer); - } - } - - LOG(TRACE) << "Exiting: " << __func__; - return true; - } - - EM_BOOL WebAssemblyViewport::OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) - { - LOG(TRACE) << __func__; - WebAssemblyViewport* that = reinterpret_cast(userData); - - if (that->compositor_.get() != NULL) - { - PointerEvent pointer; - ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); - that->controller_->HandleMouseRelease(pointer); - that->Invalidate(); - } - - LOG(TRACE) << "Exiting: " << __func__; - return true; - } - - void* WebAssemblyViewport::CreateObjectCookie() - { - boost::weak_ptr* weakThisPtr = - new boost::weak_ptr(); - - *weakThisPtr = shared_from_this(); - - void* cookie = reinterpret_cast(weakThisPtr); - - LOG(TRACE) << "WebAssemblyViewport::CreateObjectCookie() => cookie = " - << cookie << "\n"; - - return cookie; - } - - WebAssemblyViewport* WebAssemblyViewport::DereferenceObjectCookie(void* cookie) - { - LOG(TRACE) << "WebAssemblyViewport::DereferenceObjectCookie(cookie = " - << cookie << ")\n"; - - boost::weak_ptr* weakThisPtr = - reinterpret_cast*>(cookie); - - boost::shared_ptr sharedThis = weakThisPtr->lock(); - - return sharedThis.get(); - } - - void WebAssemblyViewport::ReleaseObjectCookie(void* cookie) - { - LOG(TRACE) << "WebAssemblyViewport::ReleaseObjectCookie(cookie = " - << cookie << ")\n"; - - boost::weak_ptr* weakThisPtr = - reinterpret_cast*>(cookie); - - delete weakThisPtr; - } - - void WebAssemblyViewport::Invalidate() - { - LOG(TRACE) << "WebAssemblyViewport::Invalidate()\n"; - long id = emscripten_request_animation_frame(OnRequestAnimationFrame, - CreateObjectCookie()); - //animationFrameCallbackIds_.push_back(id); - } - - void WebAssemblyViewport::FitForPrint() - { - if (compositor_.get() != NULL && - controller_ /* should always be true */) - { - RefreshCanvasSize(); - compositor_->FitContent(controller_->GetScene()); - - void* cookie = CreateObjectCookie(); - OnRequestAnimationFrame(0, cookie); // Mandatory to work with Firefox - } - } - - void WebAssemblyViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */) - { - if (compositor == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - compositor_.reset(compositor); - } - } - -#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1 -// everything OK..... we're using the new setting -#else -#pragma message("WARNING: DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR is not defined or equal to 0. Stone will use the OLD Emscripten rules for DOM element selection.") -#endif - - WebAssemblyViewport::WebAssemblyViewport( - const std::string& canvasId, bool enableEmscriptenMouseEvents) : - canvasId_(canvasId), -#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1 - canvasCssSelector_("#" + canvasId), -#else - canvasCssSelector_(canvasId), -#endif - interactor_(new DefaultViewportInteractor), - enableEmscriptenMouseEvents_(enableEmscriptenMouseEvents), - canvasWidth_(0), - canvasHeight_(0) - { - } - - void WebAssemblyViewport::PostConstructor() - { - boost::shared_ptr viewport = shared_from_this(); - controller_.reset(new ViewportController(viewport)); - - LOG(INFO) << "Initializing Stone viewport on HTML canvas: " - << canvasId_; - - if (canvasId_.empty() || - canvasId_[0] == '#') - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, - "The canvas identifier must not start with '#'"); - } - - // Disable right-click on the canvas (i.e. context menu) - EM_ASM({ - document.getElementById(UTF8ToString($0)).oncontextmenu = - function(event) - { - event.preventDefault(); - } - }, - canvasId_.c_str() // $0 - ); - - // It is not possible to monitor the resizing of individual - // canvas, so we track the full window of the browser - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, - reinterpret_cast(this), - false, - OnResize); - - if (enableEmscriptenMouseEvents_) - { - - // if any of this function causes an error in the console, please - // make sure you are using the new (as of 1.39.x) version of - // emscripten element lookup rules( pass - // "-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1" to the linker. - - emscripten_set_mousedown_callback(canvasCssSelector_.c_str(), - reinterpret_cast(this), - false, - OnMouseDown); - - emscripten_set_mousemove_callback(canvasCssSelector_.c_str(), - reinterpret_cast(this), - false, - OnMouseMove); - - emscripten_set_mouseup_callback(canvasCssSelector_.c_str(), - reinterpret_cast(this), - false, - OnMouseUp); - } - } - - WebAssemblyViewport::~WebAssemblyViewport() - { - LOG(TRACE) << "WebAssemblyViewport::~WebAssemblyViewport()\n"; - - emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, - reinterpret_cast(this), - false, - NULL); - - if (enableEmscriptenMouseEvents_) - { - - emscripten_set_mousedown_callback(canvasCssSelector_.c_str(), - reinterpret_cast(this), - false, - NULL); - - emscripten_set_mousemove_callback(canvasCssSelector_.c_str(), - reinterpret_cast(this), - false, - NULL); - - emscripten_set_mouseup_callback(canvasCssSelector_.c_str(), - reinterpret_cast(this), - false, - NULL); - } - } - - IViewport::ILock* WebAssemblyViewport::Lock() - { - return new WasmLock(*this); - } - - void WebAssemblyViewport::AcquireInteractor(IViewportInteractor* interactor) - { - if (interactor == NULL) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); - } - else - { - interactor_.reset(interactor); - } - } - - - void WebAssemblyViewport::RefreshCanvasSize() - { - double w = -1, h = -1; - EMSCRIPTEN_RESULT result = - emscripten_get_element_css_size(GetCanvasCssSelector().c_str(), &w, &h); - - if (result != EMSCRIPTEN_RESULT_SUCCESS) - { - LOG(WARNING) << "WebAssemblyViewport::RefreshCanvasSize failed to " - << "retrieve CSS size for " << GetCanvasCssSelector(); - } - - /** - * Emscripten has the function emscripten_get_element_css_size() - * to query the width and height of a named HTML element. I'm - * calling this first to get the initial size of the canvas DOM - * element, and then call emscripten_set_canvas_size() to - * initialize the framebuffer size of the canvas to the same - * size as its DOM element. - * https://floooh.github.io/2017/02/22/emsc-html.html - **/ - if (w > 0 && - h > 0) - { - canvasWidth_ = static_cast(boost::math::iround(w)); - canvasHeight_ = static_cast(boost::math::iround(h)); - } - else - { - canvasWidth_ = 0; - canvasHeight_ = 0; - } - - emscripten_set_canvas_element_size(GetCanvasCssSelector().c_str(), canvasWidth_, canvasHeight_); - - if (compositor_.get() != NULL) - { - compositor_->SetCanvasSize(canvasWidth_, canvasHeight_); - } - } -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebAssemblyViewport.h --- a/Applications/Platforms/WebAssembly/WebAssemblyViewport.h Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,180 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#pragma once - -#include "../../../OrthancStone/Sources/OrthancStone.h" - -#if !defined(ORTHANC_ENABLE_WASM) -# error Macro ORTHANC_ENABLE_WASM must be defined -#endif - -#if ORTHANC_ENABLE_WASM != 1 -# error This file can only be used if targeting WebAssembly -#endif - -#include "../../../OrthancStone/Sources/Viewport/IViewport.h" -#include "../../../OrthancStone/Sources/Viewport/IViewportInteractor.h" - -#include - -#include -#include - -#include -#include -#include -#include - -namespace OrthancStone -{ - class WebAssemblyViewport : public IViewport, - public boost::enable_shared_from_this - - { - private: - class WasmLock; - - std::string canvasId_; - std::string canvasCssSelector_; - std::unique_ptr compositor_; - std::unique_ptr controller_; - std::unique_ptr interactor_; - bool enableEmscriptenMouseEvents_; - unsigned int canvasWidth_; - unsigned int canvasHeight_; - - static EM_BOOL OnRequestAnimationFrame(double time, void *userData); - - static EM_BOOL OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData); - - static EM_BOOL OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); - - static EM_BOOL OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); - - static EM_BOOL OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); - - protected: - void Invalidate(); - - void ClearCompositor() - { - compositor_.reset(); - } - - bool HasCompositor() const - { - return compositor_.get() != NULL; - } - - void AcquireCompositor(ICompositor* compositor /* takes ownership */); - - virtual void Paint(ICompositor& compositor, - ViewportController& controller) = 0; - - /** - The second argument is temporary and should be deleted once the migration - to interactors is finished. It should be set to "true" for new applications. - */ - WebAssemblyViewport(const std::string& canvasId, - bool enableEmscriptenMouseEvents); - - void PostConstructor(); - - - /** - * This method can be called to retrieve a cookie that can be passed to - * C-style callbacks that expect the object to be passed as a void* - * - * This cookie is a resource and must be freed when it is guaranteed - * not to be used anymore, with ReleaseObjectCookie - */ - void* CreateObjectCookie(); - - /** - * This static method can be used to dereference a cookie (i.e. retrieve - * a pointer to the underlying object) that has been created with - * WebAssemblyViewport::CreateObjectCookie() - * - * If this method returns NULL, it basically means that the - * WebAssemblyViewport has already been deleted and that you should NOT - * attempt to use it! - * - * This method never releases the cookie (for other in-flight callbacks - * could possibly require to cookie to be valid) - * - * If this method is called AFTER ReleaseObjectCookie has been called on - * the same cookie, the behavior is undefined and things will CERTAINLY - * go wrong. - * - * NEVER call this method on a void* that has not been generated by the - * CreateObjectCookie method of this class - */ - static WebAssemblyViewport* DereferenceObjectCookie(void* cookie); - - /** - * This method must be used when the object cookie will not be used anymore. - * - * You must call it when you are certain that no entity will attempt to - * dereference the cookie. - */ - static void ReleaseObjectCookie(void* cookie); - - void RefreshCanvasSize(); - - unsigned int GetCanvasWidth() const - { - return canvasWidth_; - } - - unsigned int GetCanvasHeight() - { - return canvasHeight_; - } - - public: - virtual ILock* Lock() ORTHANC_OVERRIDE; - - ~WebAssemblyViewport(); - - /** - This method takes ownership - */ - void AcquireInteractor(IViewportInteractor* interactor); - - const std::string& GetCanvasId() const - { - return canvasId_; - } - - /** - emscripten functions requires the css selector for the canvas. This is - different from the canvas id (the syntax is '#mycanvasid') - */ - const std::string& GetCanvasCssSelector() const - { - return canvasCssSelector_; - } - - void FitForPrint(); - }; -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebGLViewport.cpp --- a/Applications/Platforms/WebAssembly/WebGLViewport.cpp Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,87 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#include "WebGLViewport.h" - -#include "../../../OrthancStone/Sources/StoneException.h" -#include "../../../OrthancStone/Sources/Scene2D/OpenGLCompositor.h" -#include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" - -namespace OrthancStone -{ - void WebGLViewport::Paint(ICompositor& compositor, - ViewportController& controller) - { - try - { - compositor.Refresh(controller.GetScene()); - - /** - * No need to manually swap the buffer: "Rendered WebGL content - * is implicitly presented (displayed to the user) on the canvas - * when the event handler that renders with WebGL returns back - * to the browser event loop." - * https://emscripten.org/docs/api_reference/html5.h.html#webgl-context - * - * Could call "emscripten_webgl_commit_frame()" if - * "explicitSwapControl" option were set to "true". - **/ - } - catch (const StoneException& e) - { - // Ignore problems about the loss of the WebGL context (edge case) - if (e.GetErrorCode() == ErrorCode_WebGLContextLost) - { - return; - } - else - { - throw; - } - } - } - - - WebGLViewport::WebGLViewport(const std::string& canvasId, bool enableEmscriptenMouseEvents) : - WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents), - context_(GetCanvasCssSelector()) - { - AcquireCompositor(new OpenGLCompositor(context_)); - } - - boost::shared_ptr WebGLViewport::Create( - const std::string& canvasId, bool enableEmscriptenMouseEvents) - { - boost::shared_ptr that = boost::shared_ptr( - new WebGLViewport(canvasId, enableEmscriptenMouseEvents)); - - that->WebAssemblyViewport::PostConstructor(); - return that; - } - - WebGLViewport::~WebGLViewport() - { - // Make sure to delete the compositor before its parent "context_" gets - // deleted - ClearCompositor(); - } -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebGLViewport.h --- a/Applications/Platforms/WebAssembly/WebGLViewport.h Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#pragma once - -#include "WebAssemblyOpenGLContext.h" -#include "WebAssemblyViewport.h" - -namespace OrthancStone -{ - class WebGLViewport : public WebAssemblyViewport - { - private: - OpenGL::WebAssemblyOpenGLContext context_; - - WebGLViewport(const std::string& canvasId, - bool enableEmscriptenMouseEvents); - - protected: - virtual void Paint(ICompositor& compositor, - ViewportController& controller) ORTHANC_OVERRIDE; - - public: - static boost::shared_ptr Create(const std::string& canvasId, - bool enableEmscriptenMouseEvents = true); - - virtual ~WebGLViewport(); - - bool IsContextLost() - { - return context_.IsContextLost(); - } - }; -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebGLViewportsRegistry.cpp --- a/Applications/Platforms/WebAssembly/WebGLViewportsRegistry.cpp Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,222 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#include "WebGLViewportsRegistry.h" - -#include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" -#include "../../../OrthancStone/Sources/Toolbox/GenericToolbox.h" - -#include - -#include - - -static double viewportsTimeout_ = 1000; -static std::unique_ptr globalRegistry_; - - -namespace OrthancStone -{ - void WebGLViewportsRegistry::LaunchTimer() - { - timeOutID_ = emscripten_set_timeout( - OnTimeoutCallback, - timeoutMS_, - reinterpret_cast(this)); - } - - void WebGLViewportsRegistry::OnTimeout() - { - for (Viewports::iterator it = viewports_.begin(); - it != viewports_.end(); - ++it) - { - if (it->second == NULL || - it->second->IsContextLost()) - { - LOG(INFO) << "WebGL context lost for canvas: " << it->first; - - // Try and duplicate the HTML5 canvas in the DOM - EM_ASM({ - var canvas = document.getElementById(UTF8ToString($0)); - if (canvas) { - var parent = canvas.parentElement; - if (parent) { - var cloned = canvas.cloneNode(true /* deep copy */); - parent.insertBefore(cloned, canvas); - parent.removeChild(canvas); - } - } - }, - it->first.c_str() // $0 = ID of the canvas - ); - - // At this point, the old canvas is removed from the DOM and - // replaced by a fresh one with the same ID: Recreate the - // WebGL context on the new canvas - boost::shared_ptr viewport; - - // we need to steal the properties from the old viewport - // and set them to the new viewport - { - std::unique_ptr lock(it->second->Lock()); - - // TODO: remove ViewportController - Scene2D* scene = lock->GetController().ReleaseScene(); - viewport = WebGLViewport::Create(it->first); - - { - std::unique_ptr newLock(viewport->Lock()); - newLock->GetController().AcquireScene(scene); - } - } - - // Replace the old WebGL viewport by the new one - it->second = viewport; - - // Tag the fresh canvas as needing a repaint - { - std::unique_ptr lock(it->second->Lock()); - lock->Invalidate(); - } - } - } - - LaunchTimer(); - } - - void WebGLViewportsRegistry::OnTimeoutCallback(void *userData) - { - // This object dies with the process or tab. - WebGLViewportsRegistry* that = - reinterpret_cast(userData); - that->OnTimeout(); - } - - WebGLViewportsRegistry::WebGLViewportsRegistry(double timeoutMS) : - timeoutMS_(timeoutMS), - timeOutID_(0) - { - if (timeoutMS <= 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - LaunchTimer(); - } - - WebGLViewportsRegistry::~WebGLViewportsRegistry() - { - emscripten_clear_timeout(timeOutID_); - Clear(); - } - - boost::shared_ptr WebGLViewportsRegistry::Add( - const std::string& canvasId) - { - if (viewports_.find(canvasId) != viewports_.end()) - { - LOG(ERROR) << "Canvas was already registered: " << canvasId; - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - boost::shared_ptr viewport = - WebGLViewport::Create(canvasId); - viewports_[canvasId] = viewport; - return viewport; - } - } - - void WebGLViewportsRegistry::Remove(const std::string& canvasId) - { - Viewports::iterator found = viewports_.find(canvasId); - - if (found == viewports_.end()) - { - LOG(ERROR) << "Cannot remove unregistered canvas: " << canvasId; - } - else - { - viewports_.erase(found); - } - } - - void WebGLViewportsRegistry::Clear() - { - viewports_.clear(); - } - - WebGLViewportsRegistry::Accessor::Accessor(WebGLViewportsRegistry& that, - const std::string& canvasId) : - that_(that) - { - Viewports::iterator viewport = that.viewports_.find(canvasId); - if (viewport != that.viewports_.end() && - viewport->second != NULL) - { - lock_.reset(viewport->second->Lock()); - } - } - - IViewport::ILock& WebGLViewportsRegistry::Accessor::GetViewport() const - { - if (IsValid()) - { - return *lock_; - } - else - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - } - - - void WebGLViewportsRegistry::FinalizeGlobalRegistry() - { - globalRegistry_.reset(); - } - - - void WebGLViewportsRegistry::SetGlobalRegistryTimeout(double timeout) - { - if (globalRegistry_.get()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); - } - else - { - viewportsTimeout_ = timeout; - } - } - - - WebGLViewportsRegistry& WebGLViewportsRegistry::GetGlobalRegistry() - { - if (globalRegistry_.get() == NULL) - { - globalRegistry_.reset(new WebGLViewportsRegistry(viewportsTimeout_)); - } - - return *globalRegistry_; - } -} diff -r a5e54bd87b25 -r 917500c46fe0 Applications/Platforms/WebAssembly/WebGLViewportsRegistry.h --- a/Applications/Platforms/WebAssembly/WebGLViewportsRegistry.h Wed Jan 26 19:42:04 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -/** - * Stone of Orthanc - * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics - * Department, University Hospital of Liege, Belgium - * Copyright (C) 2017-2022 Osimis S.A., Belgium - * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . - **/ - - -#pragma once - -#include "WebGLViewport.h" - -#include - -namespace OrthancStone -{ - /** - * This singleton class must be used if many WebGL viewports are - * created by the higher-level application, implying possible loss - * of WebGL contexts. The object will run an infinite update loop - * that checks whether all the WebGL context are still valid (not - * lost). If some WebGL context is lost, it is automatically - * reinitialized by created a fresh HTML5 canvas. - **/ - class WebGLViewportsRegistry : public boost::noncopyable, - public boost::enable_shared_from_this - { - private: - typedef std::map > Viewports; - - double timeoutMS_; - Viewports viewports_; - long timeOutID_; - - void LaunchTimer(); - - void OnTimeout(); - - static void OnTimeoutCallback(void *userData); - - public: - explicit WebGLViewportsRegistry(double timeoutMS /* in milliseconds */); - - ~WebGLViewportsRegistry(); - - boost::shared_ptr Add(const std::string& canvasId); - - void Remove(const std::string& canvasId); - - void Clear(); - - class Accessor : public boost::noncopyable - { - private: - WebGLViewportsRegistry& that_; - std::unique_ptr lock_; - - public: - Accessor(WebGLViewportsRegistry& that, - const std::string& canvasId); - - bool IsValid() const - { - return lock_.get() != NULL; - } - - IViewport::ILock& GetViewport() const; - }; - - - - static void FinalizeGlobalRegistry(); - - static void SetGlobalRegistryTimeout(double timeout); - - static WebGLViewportsRegistry& GetGlobalRegistry(); - }; -} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/Sdl/OrthancStoneSdlConfiguration.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/Sdl/OrthancStoneSdlConfiguration.cmake Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,76 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +##################################################################### +## Sanity check of the configuration +##################################################################### + +include(${ORTHANC_STONE_ROOT}/../Resources/CMake/OrthancStoneConfiguration.cmake) + +if (ORTHANC_SANDBOXED) + message(FATAL_ERROR "Cannot enable SDL in sandboxed environments") +endif() + +if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten" OR + CMAKE_SYSTEM_NAME STREQUAL "PNaCl" OR + CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR + CMAKE_SYSTEM_NAME STREQUAL "NaCl64") + message(FATAL_ERROR "Trying to use a Web compiler for a native build") +endif() + + + +##################################################################### +## Configure SDL +##################################################################### + +message("SDL is enabled") +include(${CMAKE_CURRENT_LIST_DIR}/SdlConfiguration.cmake) + +add_definitions( + -DORTHANC_ENABLE_SDL=1 + -DORTHANC_ENABLE_WASM=0 + ) + +if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND + NOT MSVC) + # This is necessary when compiling EXE for Windows using MinGW + link_libraries(mingw32) +endif() + + + +##################################################################### +## Additional source files for SDL +##################################################################### + +list(APPEND ORTHANC_STONE_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/SdlViewport.cpp + ${CMAKE_CURRENT_LIST_DIR}/SdlWindow.cpp + ${SDL_SOURCES} + ) + +if (ENABLE_OPENGL) + list(APPEND ORTHANC_STONE_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/SdlOpenGLContext.cpp + ) +endif() diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/Sdl/OrthancStoneSdlParameters.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/Sdl/OrthancStoneSdlParameters.cmake Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,44 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +##################################################################### +## Load generic parameters +##################################################################### + +include(${CMAKE_CURRENT_LIST_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake) + + +##################################################################### +## CMake parameters tunable by the user +##################################################################### + +# Advanced parameters to fine-tune linking against system libraries +set(USE_SYSTEM_SDL ON CACHE BOOL "Use the system version of SDL2") + + +##################################################################### +## Internal CMake parameters to enable the optional subcomponents of +## the Stone of Orthanc +##################################################################### + +set(ENABLE_THREADS ON CACHE INTERNAL "") diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/Sdl/SdlConfiguration.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/Sdl/SdlConfiguration.cmake Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,227 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +if (STATIC_BUILD OR NOT USE_SYSTEM_SDL) + SET(SDL_SOURCES_DIR ${CMAKE_BINARY_DIR}/SDL2-2.0.4) + SET(SDL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/SDL2-2.0.4.tar.gz") + SET(SDL_MD5 "44fc4a023349933e7f5d7a582f7b886e") + DownloadPackage(${SDL_MD5} ${SDL_URL} "${SDL_SOURCES_DIR}") + + include_directories(${SDL_SOURCES_DIR}/include) + + set(TMP "${SDL_SOURCES_DIR}/include/SDL_config_premake.h") + if (NOT EXISTS "${TMP}") + file(WRITE "${TMP}" " +#include \"SDL_platform.h\" +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#define HAVE_STDINT_H 1 +") + endif() + + # General source files + file(GLOB SDL_SOURCES + ${SDL_SOURCES_DIR}/src/*.c + ${SDL_SOURCES_DIR}/src/atomic/*.c + ${SDL_SOURCES_DIR}/src/audio/*.c + ${SDL_SOURCES_DIR}/src/cpuinfo/*.c + ${SDL_SOURCES_DIR}/src/dynapi/*.c + ${SDL_SOURCES_DIR}/src/events/*.c + ${SDL_SOURCES_DIR}/src/file/*.c + ${SDL_SOURCES_DIR}/src/haptic/*.c + ${SDL_SOURCES_DIR}/src/joystick/*.c + ${SDL_SOURCES_DIR}/src/libm/*.c + ${SDL_SOURCES_DIR}/src/power/*.c + ${SDL_SOURCES_DIR}/src/render/*.c + ${SDL_SOURCES_DIR}/src/stdlib/*.c + ${SDL_SOURCES_DIR}/src/thread/*.c + ${SDL_SOURCES_DIR}/src/timer/*.c + ${SDL_SOURCES_DIR}/src/video/*.c + + ${SDL_SOURCES_DIR}/src/loadso/dummy/*.c + #${SDL_SOURCES_DIR}/src/timer/dummy/*.c + ${SDL_SOURCES_DIR}/src/audio/dummy/*.c + ${SDL_SOURCES_DIR}/src/filesystem/dummy/*.c + ${SDL_SOURCES_DIR}/src/haptic/dummy/*.c + ${SDL_SOURCES_DIR}/src/joystick/dummy/*.c + #${SDL_SOURCES_DIR}/src/main/dummy/*.c + ${SDL_SOURCES_DIR}/src/video/dummy/*.c + ) + + add_definitions( + -DUSING_PREMAKE_CONFIG_H=1 + + -DSDL_AUDIO_DISABLED=1 + -DSDL_AUDIO_DRIVER_DUMMY=1 + -DSDL_FILESYSTEM_DISABLED=1 + -DSDL_FILESYSTEM_DUMMY=1 + -DSDL_FILE_DISABLED=1 + -DSDL_HAPTIC_DISABLED=1 + -DSDL_JOYSTICK_DISABLED=1 + + #-DSDL_THREADS_DISABLED=1 + ) + + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + file(GLOB TMP + ${SDL_SOURCES_DIR}/src/core/linux/*.c + ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c + ${SDL_SOURCES_DIR}/src/render/software/*.c + ${SDL_SOURCES_DIR}/src/thread/pthread/*.c + ${SDL_SOURCES_DIR}/src/timer/unix/*.c + ${SDL_SOURCES_DIR}/src/video/x11/*.c + ) + + list(APPEND SDL_SOURCES ${TMP}) + + add_definitions( + -DSDL_LOADSO_DLOPEN=1 + -DSDL_THREAD_PTHREAD=1 + -DSDL_TIMER_UNIX=1 + -DSDL_POWER_DISABLED=1 + + -DSDL_VIDEO_DRIVER_X11=1 + + -DSDL_ASSEMBLY_ROUTINES=1 + -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1 + -DSDL_VIDEO_DRIVER_X11_SUPPORTS_GENERIC_EVENTS=1 + -DHAVE_GCC_SYNC_LOCK_TEST_AND_SET=1 + ) + + link_libraries(X11 Xext) + + if (NOT CMAKE_SYSTEM_VERSION STREQUAL "Raspberry") + # Raspberry Pi has no support for OpenGL + file(GLOB TMP + ${SDL_SOURCES_DIR}/src/render/opengl/*.c + ${SDL_SOURCES_DIR}/src/render/opengles2/*.c + ) + + list(APPEND SDL_SOURCES ${TMP}) + + add_definitions( + -DSDL_VIDEO_OPENGL=1 + -DSDL_VIDEO_OPENGL_ES2=1 + -DSDL_VIDEO_RENDER_OGL=1 + -DSDL_VIDEO_RENDER_OGL_ES2=1 + -DSDL_VIDEO_OPENGL_GLX=1 + -DSDL_VIDEO_OPENGL_EGL=1 + ) + endif() + + elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") + file(GLOB TMP + ${SDL_SOURCES_DIR}/src/audio/directsound/*.c + ${SDL_SOURCES_DIR}/src/audio/disk/*.c + ${SDL_SOURCES_DIR}/src/audio/winmm/*.c + ${SDL_SOURCES_DIR}/src/joystick/windows/*.c + ${SDL_SOURCES_DIR}/src/haptic/windows/*.c + ${SDL_SOURCES_DIR}/src/power/windows/*.c + + ${SDL_SOURCES_DIR}/src/main/windows/*.c + ${SDL_SOURCES_DIR}/src/core/windows/*.c + ${SDL_SOURCES_DIR}/src/loadso/windows/*.c + ${SDL_SOURCES_DIR}/src/render/direct3d/*.c + ${SDL_SOURCES_DIR}/src/render/direct3d11/*.c + ${SDL_SOURCES_DIR}/src/render/opengl/*.c + ${SDL_SOURCES_DIR}/src/render/psp/*.c + ${SDL_SOURCES_DIR}/src/render/opengles/*.c + ${SDL_SOURCES_DIR}/src/render/opengles2/*.c + ${SDL_SOURCES_DIR}/src/render/software/*.c + ${SDL_SOURCES_DIR}/src/thread/generic/SDL_syscond.c # Don't include more files from "thread/generic/*.c"! + ${SDL_SOURCES_DIR}/src/thread/windows/*.c + ${SDL_SOURCES_DIR}/src/timer/windows/*.c + ${SDL_SOURCES_DIR}/src/video/windows/*.c + ${SDL_SOURCES_DIR}/src/windows/dlopen/*.c + ) + + list(APPEND SDL_SOURCES ${TMP}) + + # NB: OpenGL ES headers are not available in MinGW-W64 + add_definitions( + -DSDL_LOADSO_WINDOWS=1 + -DSDL_THREAD_WINDOWS=1 + -DSDL_TIMER_WINDOWS=1 + -DSDL_POWER_WINDOWS=1 + + -DSDL_VIDEO_OPENGL=1 + -DSDL_VIDEO_OPENGL_WGL=1 + -DSDL_VIDEO_RENDER_D3D=1 + -DSDL_VIDEO_RENDER_OGL=1 + -DSDL_VIDEO_DRIVER_WINDOWS=1 + ) + + if (MSVC) + add_definitions( + -D__FLTUSED__ + -DHAVE_LIBC=1 + ) + else() + add_definitions( + -DHAVE_GCC_ATOMICS=1 + -DSDL_ASSEMBLY_ROUTINES=1 + ) + endif() + + link_libraries(imm32 winmm version) + + elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin") + file(GLOB TMP + ${SDL_SOURCES_DIR}/src/loadso/dlopen/*.c + ${SDL_SOURCES_DIR}/src/render/opengl/*.c + ${SDL_SOURCES_DIR}/src/render/opengles2/*.c + ${SDL_SOURCES_DIR}/src/render/software/*.c + ${SDL_SOURCES_DIR}/src/thread/pthread/*.c + ${SDL_SOURCES_DIR}/src/timer/unix/*.c + ${SDL_SOURCES_DIR}/src/video/cocoa/*.m + ) + + list(APPEND SDL_SOURCES ${TMP}) + + add_definitions( + -DSDL_LOADSO_DLOPEN=1 + -DSDL_THREAD_PTHREAD=1 + -DSDL_TIMER_UNIX=1 + -DSDL_POWER_DISABLED=1 + + -DSDL_VIDEO_DRIVER_COCOA=1 + -DSDL_VIDEO_OPENGL=1 + -DSDL_VIDEO_OPENGL_CGL=1 + -DSDL_VIDEO_RENDER_OGL=1 + + -DSDL_ASSEMBLY_ROUTINES=1 + -DSDL_THREAD_PTHREAD_RECURSIVE_MUTEX=1 + ) + + find_library(CARBON_LIBRARY Carbon) + find_library(COCOA_LIBRARY Cocoa) + find_library(IOKIT_LIBRARY IOKit) + find_library(QUARTZ_LIBRARY QuartzCore) + link_libraries(${CARBON_LIBRARY} ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${QUARTZ_LIBRARY}) + + endif() + +else() + pkg_search_module(SDL2 REQUIRED sdl2) + include_directories(${SDL2_INCLUDE_DIRS}) + link_libraries(${SDL2_LIBRARIES}) +endif() diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/Sdl/SdlOpenGLContext.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/Sdl/SdlOpenGLContext.cpp Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,131 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#include "SdlOpenGLContext.h" +#include "../../../OrthancStone/Sources/StoneException.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 +#endif + +#include +#include + +#include + +namespace OrthancStone +{ + SdlOpenGLContext::SdlOpenGLContext(const char* title, + unsigned int width, + unsigned int height, + bool allowDpiScaling) + : window_(title, width, height, true /* enable OpenGL */, allowDpiScaling) + , context_(NULL) + { + 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) + { + LOG(ERROR) << glewGetErrorString(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) + { + const char* errText = SDL_GetError(); + std::stringstream ss; + ss << "Cannot set current OpenGL context. SDL error text: " << errText; + std::string errStr = ss.str(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, errStr.c_str()); + } + + // This makes our buffer swap synchronized 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(w); + } + + + unsigned int SdlOpenGLContext::GetCanvasHeight() const + { + int h = 0; + SDL_GL_GetDrawableSize(window_.GetObject(), NULL, &h); + return static_cast(h); + } +} + +#endif diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/Sdl/SdlOpenGLContext.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/Sdl/SdlOpenGLContext.h Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,78 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#pragma once + +#if ORTHANC_ENABLE_SDL == 1 + +#include "../../../OrthancStone/Sources/OpenGL/IOpenGLContext.h" +#include "SdlWindow.h" + +#include // For ORTHANC_OVERRIDE + +#include + +#include + +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 bool IsContextLost() ORTHANC_OVERRIDE + { + // On desktop applications, an OpenGL context should never be lost + return false; + } + + virtual void MakeCurrent() ORTHANC_OVERRIDE; + + virtual void SwapBuffer() ORTHANC_OVERRIDE; + + unsigned int GetCanvasWidth() const; + + unsigned int GetCanvasHeight() const; + + void ToggleMaximize() + { + window_.ToggleMaximize(); + } + }; +} + +#endif diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/Sdl/SdlViewport.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/Sdl/SdlViewport.cpp Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,264 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#include "SdlViewport.h" + +#include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" + +#include + +#include + +namespace OrthancStone +{ + ICompositor& SdlViewport::SdlLock::GetCompositor() + { + if (that_.compositor_.get() == NULL) + { + // The derived class should have called "AcquireCompositor()" + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + return *that_.compositor_; + } + } + + + void SdlViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */) + { + if (compositor == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + compositor_.reset(compositor); + } + + + SdlViewport::SdlViewport() + { + refreshEvent_ = SDL_RegisterEvents(1); + + if (refreshEvent_ == static_cast(-1)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + void SdlViewport::PostConstructor() + { + controller_ = boost::make_shared(shared_from_this()); + } + + void SdlViewport::SendRefreshEvent() + { + SDL_Event event; + SDL_memset(&event, 0, sizeof(event)); + event.type = refreshEvent_; + SDL_PushEvent(&event); // This function is thread-safe, and can be called from other threads safely. + } + + + void SdlViewport::UpdateSize(unsigned int width, unsigned int height) + { + SdlLock lock(*this); + lock.GetCompositor().SetCanvasSize(width, height); + lock.Invalidate(); + } + + +#if ORTHANC_ENABLE_OPENGL == 1 + SdlOpenGLViewport::SdlOpenGLViewport(const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling) : + context_(title.c_str(), width, height, allowDpiScaling) + { + AcquireCompositor(new OpenGLCompositor(context_)); // (*) + } +#endif + + +#if ORTHANC_ENABLE_OPENGL == 1 + void SdlOpenGLViewport::RefreshCanvasSize() + { + UpdateSize(context_.GetCanvasWidth(), context_.GetCanvasHeight()); + } +#endif + + +#if ORTHANC_ENABLE_OPENGL == 1 + boost::shared_ptr SdlOpenGLViewport::Create(const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling) + { + boost::shared_ptr that = + boost::shared_ptr(new SdlOpenGLViewport(title, width, height, allowDpiScaling)); + that->SdlViewport::PostConstructor(); + return that; + } +#endif + + +#if ORTHANC_ENABLE_OPENGL == 1 + uint32_t SdlOpenGLViewport::GetSdlWindowId() + { + const SdlWindow& sdlWindowWrapper = context_.GetWindow(); + SDL_Window* sdlWindow = sdlWindowWrapper.GetObject(); + Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow); + return sdlWindowId; + } +#endif + + +#if ORTHANC_ENABLE_OPENGL == 1 + SdlOpenGLViewport::~SdlOpenGLViewport() + { + // Make sure that the "OpenGLCompositor" is destroyed BEFORE the + // "OpenGLContext" it references (*) + ClearCompositor(); + } +#endif + + +#if ORTHANC_ENABLE_OPENGL == 1 + void SdlOpenGLViewport::Paint() + { + SdlLock lock(*this); + lock.GetCompositor().Refresh(lock.GetController().GetScene()); + } +#endif + + +#if ORTHANC_ENABLE_OPENGL == 1 + void SdlOpenGLViewport::ToggleMaximize() + { + // No need to call "Invalidate()" here, as "UpdateSize()" will + // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED" + SdlLock lock(*this); + context_.ToggleMaximize(); + } +#endif + + + void SdlCairoViewport::RefreshCanvasSize() + { + UpdateSize(window_.GetWidth(), window_.GetHeight()); + } + + SdlCairoViewport::SdlCairoViewport(const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling) : + window_(title, width, height, false /* enable OpenGL */, allowDpiScaling), + sdlSurface_(NULL) + { + AcquireCompositor(new CairoCompositor(width, height)); + } + + SdlCairoViewport::~SdlCairoViewport() + { + if (sdlSurface_) + { + SDL_FreeSurface(sdlSurface_); + } + } + + uint32_t SdlCairoViewport::GetSdlWindowId() + { + SDL_Window* sdlWindow = window_.GetObject(); + Uint32 sdlWindowId = SDL_GetWindowID(sdlWindow); + return sdlWindowId; + } + + void SdlCairoViewport::Paint() + { + SdlLock lock(*this); + + lock.GetCompositor().Refresh(lock.GetController().GetScene()); + CreateSdlSurfaceFromCompositor(dynamic_cast(lock.GetCompositor())); + + if (sdlSurface_ != NULL) + { + window_.Render(sdlSurface_); + } + } + + + void SdlCairoViewport::ToggleMaximize() + { + // No need to call "Invalidate()" here, as "UpdateSize()" will + // be invoked after event "SDL_WINDOWEVENT_SIZE_CHANGED" + SdlLock lock(*this); + window_.ToggleMaximize(); + } + + + // Assumes that the mutex is locked + void SdlCairoViewport::CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor) + { + static const uint32_t rmask = 0x00ff0000; + static const uint32_t gmask = 0x0000ff00; + static const uint32_t bmask = 0x000000ff; + + const unsigned int width = compositor.GetCanvas().GetWidth(); + const unsigned int height = compositor.GetCanvas().GetHeight(); + + if (sdlSurface_ != NULL) + { + if (sdlSurface_->pixels == compositor.GetCanvas().GetBuffer() && + sdlSurface_->w == static_cast(width) && + sdlSurface_->h == static_cast(height) && + sdlSurface_->pitch == static_cast(compositor.GetCanvas().GetPitch())) + { + // The image from the compositor has not changed, no need to update the surface + return; + } + else + { + SDL_FreeSurface(sdlSurface_); + } + } + + sdlSurface_ = SDL_CreateRGBSurfaceFrom((void*)(compositor.GetCanvas().GetBuffer()), width, height, 32, + compositor.GetCanvas().GetPitch(), rmask, gmask, bmask, 0); + if (!sdlSurface_) + { + LOG(ERROR) << "Cannot create a SDL surface from a Cairo surface"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + boost::shared_ptr SdlCairoViewport::Create(const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling) + { + boost::shared_ptr that = + boost::shared_ptr(new SdlCairoViewport(title, width, height, allowDpiScaling)); + that->SdlViewport::PostConstructor(); + return that; + } +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/Sdl/SdlViewport.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/Sdl/SdlViewport.h Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,205 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + +#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 + +#include "SdlOpenGLContext.h" +#include "../../../OrthancStone/Sources/Scene2D/CairoCompositor.h" +#include "../../../OrthancStone/Sources/Viewport/IViewport.h" + +#if ORTHANC_ENABLE_OPENGL == 1 +# include "../../../OrthancStone/Sources/Scene2D/OpenGLCompositor.h" +#endif + +#include + +// TODO: required for UndoStack injection +// I don't like it either :) +#include + +#include +#include + +namespace OrthancStone +{ + class UndoStack; + + class SdlViewport : public IViewport, + public boost::enable_shared_from_this + { + private: + boost::recursive_mutex mutex_; + uint32_t refreshEvent_; + boost::shared_ptr controller_; + std::unique_ptr compositor_; + + void SendRefreshEvent(); + + protected: + class SdlLock : public ILock + { + private: + SdlViewport& that_; + boost::recursive_mutex::scoped_lock lock_; + + public: + explicit SdlLock(SdlViewport& that) : + that_(that), + lock_(that.mutex_) + { + } + + virtual bool HasCompositor() const ORTHANC_OVERRIDE + { + return true; + } + + virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE; + + virtual ViewportController& GetController() ORTHANC_OVERRIDE + { + return *that_.controller_; + } + + virtual void Invalidate() ORTHANC_OVERRIDE + { + that_.SendRefreshEvent(); + } + + virtual void RefreshCanvasSize() ORTHANC_OVERRIDE + { + that_.RefreshCanvasSize(); + } + }; + + void ClearCompositor() + { + compositor_.reset(); + } + + void AcquireCompositor(ICompositor* compositor /* takes ownership */); + + virtual void RefreshCanvasSize() = 0; + + protected: + SdlViewport(); + + void PostConstructor(); + + public: + bool IsRefreshEvent(const SDL_Event& event) const + { + return (event.type == refreshEvent_); + } + + virtual ILock* Lock() ORTHANC_OVERRIDE + { + return new SdlLock(*this); + } + + virtual uint32_t GetSdlWindowId() = 0; + + void UpdateSize(unsigned int width, + unsigned int height); + + virtual void ToggleMaximize() = 0; + + // Must be invoked from the main SDL thread + virtual void Paint() = 0; + }; + + +#if ORTHANC_ENABLE_OPENGL == 1 + class SdlOpenGLViewport : public SdlViewport + { + private: + SdlOpenGLContext context_; + + SdlOpenGLViewport(const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling = true); + + protected: + virtual void RefreshCanvasSize() ORTHANC_OVERRIDE; + + public: + static boost::shared_ptr Create(const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling = true); + + + virtual ~SdlOpenGLViewport(); + + virtual uint32_t GetSdlWindowId() ORTHANC_OVERRIDE; + + virtual void Paint() ORTHANC_OVERRIDE; + + virtual void ToggleMaximize() ORTHANC_OVERRIDE; + }; +#endif + + + class SdlCairoViewport : public SdlViewport + { + private: + SdlWindow window_; + SDL_Surface* sdlSurface_; + + void CreateSdlSurfaceFromCompositor(const CairoCompositor& compositor); + + SdlCairoViewport(const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling); + + protected: + virtual void RefreshCanvasSize() ORTHANC_OVERRIDE; + + public: + static boost::shared_ptr Create(const std::string& title, + unsigned int width, + unsigned int height, + bool allowDpiScaling = true); + + virtual ~SdlCairoViewport(); + + virtual uint32_t GetSdlWindowId() ORTHANC_OVERRIDE; + + virtual void Paint() ORTHANC_OVERRIDE; + + virtual void ToggleMaximize() ORTHANC_OVERRIDE; + }; +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/Sdl/SdlWindow.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/Sdl/SdlWindow.cpp Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,212 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#include "SdlWindow.h" + +#if ORTHANC_ENABLE_SDL == 1 + +#include +#include + +#ifdef WIN32 +#include // for SetProcessDpiAware +#endif +// WIN32 + +#include +#include +#include + +namespace OrthancStone +{ + SdlWindow::SdlWindow(const std::string& title, + unsigned int width, + unsigned int height, + bool enableOpenGl, + bool allowDpiScaling) : + maximized_(false) + { + // TODO Understand why, with SDL_WINDOW_OPENGL + MinGW32 + Release + // build mode, the application crashes whenever the SDL window is + // resized or maximized + + uint32_t windowFlags, rendererFlags; + if (enableOpenGl) + { + windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; + rendererFlags = SDL_RENDERER_ACCELERATED; + } + else + { + windowFlags = SDL_WINDOW_RESIZABLE; + rendererFlags = SDL_RENDERER_SOFTWARE; + } + +// TODO: probably required on MacOS X, too +#if defined(WIN32) && (_WIN32_WINNT >= 0x0600) + if (!allowDpiScaling) + { + // if we do NOT allow DPI scaling, it means an SDL pixel will be a real + // monitor pixel. This is needed for high-DPI applications + + // Enable high-DPI support on Windows + + // THE FOLLOWING HAS BEEN COMMENTED OUT BECAUSE IT WILL CRASH UNDER + // OLD WINDOWS VERSIONS + // ADD THIS AT THE TOP TO ENABLE IT: + // + //#pragma comment(lib, "Shcore.lib") THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness + //#include + //#include THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness + //#include THIS IS ONLY REQUIRED FOR SetProcessDpiAwareness + // SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE); + + // This is supported on Vista+ + SetProcessDPIAware(); + + windowFlags |= SDL_WINDOW_ALLOW_HIGHDPI; + } +#endif +// WIN32 + + window_ = SDL_CreateWindow(title.c_str(), + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + width, height, windowFlags); + + if (window_ == NULL) + { + LOG(ERROR) << "Cannot create the SDL window: " << SDL_GetError(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + renderer_ = SDL_CreateRenderer(window_, -1, rendererFlags); + if (!renderer_) + { + LOG(ERROR) << "Cannot create the SDL renderer: " << SDL_GetError(); + SDL_DestroyWindow(window_); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + SdlWindow::~SdlWindow() + { + if (renderer_ != NULL) + { + SDL_DestroyRenderer(renderer_); + } + + if (window_ != NULL) + { + SDL_DestroyWindow(window_); + } + } + + + unsigned int SdlWindow::GetWidth() const + { + int w = -1; + SDL_GetWindowSize(window_, &w, NULL); + + if (w < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + return static_cast(w); + } + } + + + unsigned int SdlWindow::GetHeight() const + { + int h = -1; + SDL_GetWindowSize(window_, NULL, &h); + + if (h < 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + else + { + return static_cast(h); + } + } + + + void SdlWindow::Render(SDL_Surface* surface) + { + /** + * "You are strongly encouraged to call SDL_RenderClear() to + * initialize the backbuffer before starting each new frame's + * drawing, even if you plan to overwrite every pixel." + * https://wiki.libsdl.org/SDL_RenderPresent + **/ + SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 255); + SDL_RenderClear(renderer_); // Clear the entire screen to our selected color + + SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer_, surface); + if (texture != NULL) + { + SDL_RenderCopy(renderer_, texture, NULL, NULL); + SDL_DestroyTexture(texture); + } + + SDL_RenderPresent(renderer_); + } + + + void SdlWindow::ToggleMaximize() + { + if (maximized_) + { + SDL_RestoreWindow(window_); + maximized_ = false; + } + else + { + SDL_MaximizeWindow(window_); + maximized_ = true; + } + } + + + void SdlWindow::GlobalInitialize() + { + if (SDL_Init(SDL_INIT_VIDEO) != 0) + { + LOG(ERROR) << "Cannot initialize SDL"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + void SdlWindow::GlobalFinalize() + { + SDL_Quit(); + } +} + +#endif diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/Sdl/SdlWindow.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/Sdl/SdlWindow.h Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,79 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#pragma once + +#if ORTHANC_ENABLE_SDL == 1 + +#include +#include + +// Forward declaration of SDL type to avoid clashes with DCMTK headers +// on "typedef Sint8", in "StoneInitialization.cpp" +struct SDL_Window; +struct SDL_Renderer; +struct SDL_Surface; + +namespace OrthancStone +{ + class SdlWindow : public boost::noncopyable + { + private: + struct SDL_Window *window_; + struct SDL_Renderer *renderer_; + bool maximized_; + + public: + SdlWindow(const std::string& title, + unsigned int width, + unsigned int height, + bool enableOpenGl, + bool allowDpiScaling = true); + + ~SdlWindow(); + + SDL_Window *GetObject() const + { + return window_; + } + + unsigned int GetWidth() const; + + unsigned int GetHeight() const; + + /** + * WARNING: "Refresh()" cannot only be called from the main SDL + * thread, in which the window was created. Otherwise, the + * renderer displays nothing! + **/ + void Render(struct SDL_Surface* surface); + + void ToggleMaximize(); + + static void GlobalInitialize(); + + static void GlobalFinalize(); + }; +} + +#endif diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/OrthancStoneWebAssemblyConfiguration.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/OrthancStoneWebAssemblyConfiguration.cmake Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,63 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + + +##################################################################### +## Sanity check of the configuration +##################################################################### + +set(ENABLE_WEB_CLIENT OFF) +include(${ORTHANC_STONE_ROOT}/../Resources/CMake/OrthancStoneConfiguration.cmake) + +if (NOT ORTHANC_SANDBOXED) + message(FATAL_ERROR "WebAssembly target must me configured as sandboxed") +endif() + +if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + message(FATAL_ERROR "WebAssembly target requires the emscripten compiler") +endif() + +add_definitions( + -DORTHANC_ENABLE_SDL=0 + -DORTHANC_ENABLE_WASM=1 + ) + + +##################################################################### +## Additional source files for WebAssembly +##################################################################### + +list(APPEND ORTHANC_STONE_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyCairoViewport.cpp + ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyLoadersContext.cpp + ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyOracle.cpp + ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyViewport.cpp + ) + +if (ENABLE_OPENGL) + list(APPEND ORTHANC_STONE_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/WebAssemblyOpenGLContext.cpp + ${CMAKE_CURRENT_LIST_DIR}/WebGLViewport.cpp + ${CMAKE_CURRENT_LIST_DIR}/WebGLViewportsRegistry.cpp + ) +endif() diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/OrthancStoneWebAssemblyParameters.cmake --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/OrthancStoneWebAssemblyParameters.cmake Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,44 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + + +##################################################################### +## Load generic parameters +##################################################################### + +include(${CMAKE_CURRENT_LIST_DIR}/../../../OrthancStone/Resources/CMake/OrthancStoneParameters.cmake) + + +##################################################################### +## CMake parameters tunable by the user +##################################################################### + +# None for now, but might contain stuff like memory settings + + +##################################################################### +## Internal CMake parameters to enable the optional subcomponents of +## the Stone of Orthanc +##################################################################### + +set(ENABLE_THREADS OFF CACHE INTERNAL "") diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/SharedLibrary/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/SharedLibrary/CMakeLists.txt Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,214 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +cmake_minimum_required(VERSION 2.8.3) +cmake_policy(SET CMP0058 NEW) + +project(OrthancStoneModule) + + +# Warning message related to WebAssembly modules: We know that 1.38.41 +# DOES NOT work, but that 1.39.17 works. +message("") +message("=== IMPORTANT: Make sure to use a recent version of Emscripten (preferably >= 2.0.0) ===") +message("") + + +set(ORTHANC_STONE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/../../../../wasm-binaries/OrthancStoneModule" CACHE PATH "Where to put the WebAssembly binaries") + + +# Ask for the generation of a side module +set(WASM_FLAGS "-s SIDE_MODULE=1 -s EXPORT_ALL=1") # Must be before "Compiler.cmake" + + +# Configuration of the Emscripten compiler for WebAssembly target +# --------------------------------------------------------------- + +set(USE_WASM ON CACHE BOOL "") + +set(WASM_FLAGS "${WASM_FLAGS} -s WASM=1 -s FETCH=1") +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") +endif() + +set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") +set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") +add_definitions( + -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 +) + + +# Stone of Orthanc configuration +# --------------------------------------------------------------- + +include(${CMAKE_SOURCE_DIR}/../OrthancStoneWebAssemblyParameters.cmake) + +SET(ENABLE_DCMTK ON) +SET(ENABLE_DCMTK_NETWORKING OFF) +SET(ENABLE_DCMTK_TRANSCODING OFF) +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) # Necessary for text rendering +SET(ORTHANC_SANDBOXED ON) + +include(${CMAKE_SOURCE_DIR}/../OrthancStoneWebAssemblyConfiguration.cmake) + + + + +################################################################################ + +# The source files that register a callback cannot be part of a side +# module, and must be compiled in the main module. The following +# command can be used to identify such files: +# $ grep -lrE 'emscripten_' ../../Sources/ + +set(SOURCES_WITH_EMSCRIPTEN_CALLBACKS + ${CMAKE_SOURCE_DIR}/../WebAssemblyOracle.cpp + ${CMAKE_SOURCE_DIR}/../WebAssemblyViewport.cpp + ${CMAKE_SOURCE_DIR}/../WebAssemblyCairoViewport.cpp + ) + +list(REMOVE_ITEM ORTHANC_STONE_SOURCES + ${SOURCES_WITH_EMSCRIPTEN_CALLBACKS} + ) + +configure_file( + ${ORTHANC_STONE_ROOT}/../SharedLibrary/OrthancStone.h.in + ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone/OrthancStone.h + ) + +configure_file( + ${ORTHANC_FRAMEWORK_ROOT}/../SharedLibrary/OrthancFramework.h.in + ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework/OrthancFramework.h + ) + +file( + COPY ${CMAKE_SOURCE_DIR}/../../../../OrthancStone/Sources/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone + NO_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "*.h" + PATTERN OrthancStone.h EXCLUDE + PATTERN "Deprecated*" EXCLUDE + ) + +file( + COPY ${ORTHANC_FRAMEWORK_ROOT}/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework + NO_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "*.h" + PATTERN OrthancFramework.h EXCLUDE + ) + +add_executable(OrthancStoneModule + ${ORTHANC_STONE_SOURCES} + ${AUTOGENERATED_SOURCES} + ${CAIRO_SOURCES} + ${PIXMAN_SOURCES} + ${FREETYPE_SOURCES} + ) + +set_target_properties(OrthancStoneModule + PROPERTIES + COMPILE_FLAGS "${WASM_FLAGS}" + LINK_FLAGS "${WASM_LINKER_FLAGS}" + ) + +# CMake does not natively handle SIDE_MODULE, and believes that +# Emscripten produces a ".js" file (whereas it creates only the +# ".wasm"). Create a dummy ".js" for target to work. +add_custom_command( + TARGET OrthancStoneModule POST_BUILD + COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/OrthancStoneModule.js + ) + +file( + COPY ${BOOST_SOURCES_DIR}/boost/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/boost/ + NO_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "*.h" + PATTERN "*.hpp" + PATTERN "*.ipp" + ) + +file( + COPY ${JSONCPP_SOURCES_DIR}/include/json/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/json/ + NO_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "*.h" + ) + +file( + COPY ${CAIRO_SOURCES_DIR}/src/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/cairo/ + NO_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "*.h" + ) + +set(DCMTK_MODULES + dcmdata + config + ofstd + oflog + ) + +foreach (module IN LISTS DCMTK_MODULES) + file( + COPY ${DCMTK_SOURCES_DIR}/ofstd/include/dcmtk/${module}/ + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk/${module}/ + NO_SOURCE_PERMISSIONS + FILES_MATCHING + PATTERN "*.h" + ) +endforeach() + + +install( + TARGETS OrthancStoneModule + DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/lib + ) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/OrthancStoneModule.wasm + DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/lib + ) + +install( + DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/Include/boost + ${CMAKE_CURRENT_BINARY_DIR}/Include/cairo + ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk + ${CMAKE_CURRENT_BINARY_DIR}/Include/json + ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework + ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-stone + DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/include/ + ) + +install(FILES + ${SOURCES_WITH_EMSCRIPTEN_CALLBACKS} + DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/src/orthanc-stone + ) diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/SharedLibrary/NOTES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/SharedLibrary/NOTES.txt Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,18 @@ + +Install Emscripten: +https://emscripten.org/docs/getting_started/downloads.html + +# cd ~/Downloads +# git clone https://github.com/emscripten-core/emsdk.git +# cd emsdk +# ./emsdk install 2.0.0 +# ./emsdk activate 2.0.0 + + +Then, if the installation path was "~/Downloads/emsdk/": + +# source ~/Downloads/emsdk/emsdk_env.sh +# cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja +# ninja install + +=> The binaries will be put in "../../../../wasm-binaries/" diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/UnitTests/CMakeLists.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/UnitTests/CMakeLists.txt Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,140 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +cmake_minimum_required(VERSION 2.8.3) +cmake_policy(SET CMP0058 NEW) + +project(OrthancStone) + +set(ORTHANC_FRAMEWORK_DEFAULT_VERSION "mainline") +set(ORTHANC_FRAMEWORK_DEFAULT_SOURCE "hg") +set(ORTHANC_STONE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/../../../../wasm-binaries/" CACHE PATH "Where to put the WebAssembly binaries") + + +# Configuration of the Emscripten compiler for WebAssembly target +# --------------------------------------------------------------- +set(USE_WASM ON CACHE BOOL "") + +set(WASM_FLAGS "-s WASM=1 -s FETCH=1 -s ASSERTIONS=1 -s DISABLE_EXCEPTION_CATCHING=0") +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1") +endif() + +set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") +set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") +set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s ALLOW_MEMORY_GROWTH=1 -s TOTAL_MEMORY=268435456") # 256MB + resize +set(WASM_LINKER_FLAGS "${WASM_LINKER_FLAGS} -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1") +add_definitions( + -DDISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 +) + + +# Stone of Orthanc configuration +# --------------------------------------------------------------- +set(ALLOW_DOWNLOADS ON) + +include(${CMAKE_SOURCE_DIR}/../OrthancStoneWebAssemblyParameters.cmake) + +SET(ENABLE_DCMTK ON) # Necessary for the tests of the Orthanc framework +SET(ENABLE_PUGIXML ON) # Necessary for the tests of the Orthanc framework +SET(ENABLE_GOOGLE_TEST OFF) +SET(ENABLE_LOCALE ON) # Necessary for text rendering +SET(ENABLE_LUA ON) # Necessary for the tests of the Orthanc framework +SET(ORTHANC_SANDBOXED ON) + +# this will set up the build system for Stone of Orthanc and will +# populate the ORTHANC_STONE_SOURCES CMake variable +include(${CMAKE_SOURCE_DIR}/../OrthancStoneWebAssemblyConfiguration.cmake) + + +################################################################################ + +# Define the WASM module +# --------------------------------------------------------------- + +set(USE_SYSTEM_GOOGLE_TEST OFF CACHE BOOL "Use the system version of Google Test") +set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)") +mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE) +include(${ORTHANC_STONE_ROOT}/../Resources/Orthanc/CMake/DownloadPackage.cmake) +include(${ORTHANC_STONE_ROOT}/../Resources/Orthanc/CMake/GoogleTestConfiguration.cmake) + + +include(${CMAKE_SOURCE_DIR}/../../../../OrthancStone/UnitTestsSources/UnitTestsSources.cmake) + +EmbedResources( + RT_STRUCT_00 ${CMAKE_SOURCE_DIR}/../../../../OrthancStone/UnitTestsSources/TestStructureSet.json + ) + +add_executable(UnitTests + # Testing Stone + ${UNIT_TESTS_SOURCES} + + # Testing the Orthanc Framework + ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/DicomMapTests.cpp + ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/FrameworkTests.cpp + ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/FromDcmtkTests.cpp + ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/ImageProcessingTests.cpp + ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/ImageTests.cpp + ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/JpegLosslessTests.cpp + ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/LoggingTests.cpp + ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/LuaTests.cpp + ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/RestApiTests.cpp + ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/StreamTests.cpp + ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/ToolboxTests.cpp + + # Tests that are not compatible with WebAssembly + + # ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/FileStorageTests.cpp + # ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/JobsTests.cpp + # ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/MemoryCacheTests.cpp + # ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/SQLiteChromiumTests.cpp + # ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/SQLiteTests.cpp + # ${ORTHANC_FRAMEWORK_ROOT}/../UnitTestsSources/ZipTests.cpp + + # Shared files + ${AUTOGENERATED_SOURCES} + ${BOOST_EXTENDED_SOURCES} + ${GOOGLE_TEST_SOURCES} + ${ORTHANC_STONE_SOURCES} + ) + + +# Declare installation files for the module +# --------------------------------------------------------------- +install( + TARGETS UnitTests + RUNTIME DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/UnitTests/ + ) + +# Declare installation files for the companion files (web scaffolding) +# please note that ${CMAKE_CURRENT_BINARY_DIR}/RtViewerWasm.js +# (the generated JS loader for the WASM module) is handled by the `install1` +# section above: it is considered to be the binary output of +# the linker. +# --------------------------------------------------------------- +install( + FILES + ${CMAKE_SOURCE_DIR}/index.html + ${CMAKE_CURRENT_BINARY_DIR}/UnitTests.wasm + DESTINATION ${ORTHANC_STONE_INSTALL_PREFIX}/UnitTests + ) diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/UnitTests/NOTES.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/UnitTests/NOTES.txt Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,18 @@ +Native compilation (without Docker) +=================================== + +Install Emscripten: +https://emscripten.org/docs/getting_started/downloads.html + +Then, if the installation path is "~/Downloads/emsdk/": + +# source ~/Downloads/emsdk/emsdk_env.sh +# mkdir Build && cd Build +# cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DALLOW_DOWNLOADS=ON -G Ninja +# ninja install + +=> The binaries will be put in "../../../../../wasm-binaries/UnitTests/" + +# cd `pwd`/../../../../../wasm-binaries/UnitTests +# python3 -m http.server 8000 +# firefox http://localhost:8000/index.html diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/UnitTests/index.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/UnitTests/index.html Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,14 @@ + + + + + Stone of Orthanc + + +

Stone of Orthanc - Unit tests

+
+      Running the tests...
+    
+ + + diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyCairoViewport.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyCairoViewport.cpp Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,119 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1 +# include "WebAssemblyCairoViewport.h" +# include "../../../OrthancStone/Sources/Scene2D/CairoCompositor.h" +# include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" +#else +// This is the case when using the WebAssembly side module, and this +// source file must be compiled within the WebAssembly main module +# include +# include +# include +#endif + + +#include + +namespace OrthancStone +{ + void WebAssemblyCairoViewport::Paint(ICompositor& compositor, + ViewportController& controller) + { + compositor.Refresh(controller.GetScene()); + + // Create a temporary memory buffer for the canvas in JavaScript + Orthanc::ImageAccessor cairo; + dynamic_cast(compositor).GetCanvas().GetReadOnlyAccessor(cairo); + + const unsigned int width = cairo.GetWidth(); + const unsigned int height = cairo.GetHeight(); + + if (javascript_.get() == NULL || + javascript_->GetWidth() != width || + javascript_->GetHeight() != height) + { + javascript_.reset(new Orthanc::Image(Orthanc::PixelFormat_RGBA32, width, height, + true /* force minimal pitch */)); + } + + // Convert from BGRA32 memory layout (only color mode supported + // by Cairo, which corresponds to CAIRO_FORMAT_ARGB32) to RGBA32 + // (as expected by HTML5 canvas). This simply amounts to + // swapping the B and R channels. Alpha channel is also set to + // full opacity (255). + uint8_t* q = reinterpret_cast(javascript_->GetBuffer()); + for (unsigned int y = 0; y < height; y++) + { + const uint8_t* p = reinterpret_cast(cairo.GetConstRow(y)); + for (unsigned int x = 0; x < width; x++) + { + q[0] = p[2]; // R + q[1] = p[1]; // G + q[2] = p[0]; // B + q[3] = 255; // A + + p += 4; + q += 4; + } + } + + if (width != 0 && + height != 0) + { + // Execute JavaScript commands to blit the image buffer onto the + // 2D drawing context of the HTML5 canvas + EM_ASM({ + const data = new Uint8ClampedArray(Module.HEAP8.buffer, $1, 4 * $2 * $3); + const img = new ImageData(data, $2, $3); + const ctx = document.getElementById(UTF8ToString($0)).getContext('2d'); + ctx.putImageData(img, 0, 0); + }, + GetCanvasId().c_str(), // $0 + javascript_->GetBuffer(), // $1 + javascript_->GetWidth(), // $2 + javascript_->GetHeight()); // $3 + } + } + + + WebAssemblyCairoViewport::WebAssemblyCairoViewport(const std::string& canvasId, + bool enableEmscriptenMouseEvents) : + WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents) + { + RefreshCanvasSize(); + AcquireCompositor(new CairoCompositor(GetCanvasWidth(), GetCanvasHeight())); + } + + + boost::shared_ptr WebAssemblyCairoViewport::Create( + const std::string& canvasId, bool enableEmscriptenMouseEvents) + { + boost::shared_ptr that = boost::shared_ptr( + new WebAssemblyCairoViewport(canvasId, enableEmscriptenMouseEvents)); + + that->WebAssemblyViewport::PostConstructor(); + return that; + } +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyCairoViewport.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyCairoViewport.h Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,50 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, 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 . + **/ + + +#pragma once + +#include "WebAssemblyViewport.h" + +namespace OrthancStone +{ + class WebAssemblyCairoViewport : public WebAssemblyViewport + { + private: + std::unique_ptr javascript_; + + WebAssemblyCairoViewport(const std::string& canvasId, + bool enableEmscriptenMouseEvents); + + protected: + virtual void Paint(ICompositor& compositor, + ViewportController& controller) ORTHANC_OVERRIDE; + + public: + static boost::shared_ptr Create(const std::string& canvasId, + bool enableEmscriptenMouseEvents = true); + + virtual ~WebAssemblyCairoViewport() + { + ClearCompositor(); + } + }; +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.cpp Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,99 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#include "WebAssemblyLoadersContext.h" + +namespace OrthancStone +{ + class WebAssemblyLoadersContext::Locker : public ILoadersContext::ILock + { + private: + WebAssemblyLoadersContext& that_; + + public: + explicit Locker(WebAssemblyLoadersContext& that) : + that_(that) + { + } + + virtual ILoadersContext& GetContext() const ORTHANC_OVERRIDE + { + return that_; + } + + virtual IObservable& GetOracleObservable() const ORTHANC_OVERRIDE + { + return that_.oracle_.GetOracleObservable(); + } + + virtual void Schedule(boost::shared_ptr receiver, + int priority, + IOracleCommand* command /* Takes ownership */) ORTHANC_OVERRIDE + { + that_.scheduler_->Schedule(receiver, priority, command); + } + + virtual void CancelRequests(boost::shared_ptr receiver) ORTHANC_OVERRIDE + { + that_.scheduler_->CancelRequests(receiver); + } + + virtual void CancelAllRequests() ORTHANC_OVERRIDE + { + that_.scheduler_->CancelAllRequests(); + } + + virtual void AddLoader(boost::shared_ptr loader) ORTHANC_OVERRIDE + { + that_.loaders_.push_back(loader); + } + + virtual void GetStatistics(uint64_t& scheduledCommands, + uint64_t& processedCommands) ORTHANC_OVERRIDE + { + scheduledCommands = that_.scheduler_->GetTotalScheduled(); + processedCommands = that_.scheduler_->GetTotalProcessed(); + } + }; + + + WebAssemblyLoadersContext::WebAssemblyLoadersContext(unsigned int maxHighPriority, + unsigned int maxStandardPriority, + unsigned int maxLowPriority) + { + oracle_.GetOracleObservable(); + scheduler_ = OracleScheduler::Create(oracle_, oracle_.GetOracleObservable(), oracle_, + maxHighPriority, maxStandardPriority, maxLowPriority); + + if (!scheduler_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + } + + + ILoadersContext::ILock* WebAssemblyLoadersContext::Lock() + { + return new Locker(*this); + } +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyLoadersContext.h Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,70 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#pragma once + +#include "../../../OrthancStone/Sources/Loaders/ILoadersContext.h" +#include "../../../OrthancStone/Sources/Loaders/OracleScheduler.h" +#include "WebAssemblyOracle.h" + +#include + +namespace OrthancStone +{ + class WebAssemblyLoadersContext : public ILoadersContext + { + private: + class Locker; + + WebAssemblyOracle oracle_; + boost::shared_ptr scheduler_; + std::list< boost::shared_ptr > loaders_; + + public: + WebAssemblyLoadersContext(unsigned int maxHighPriority, + unsigned int maxStandardPriority, + unsigned int maxLowPriority); + + void SetLocalOrthanc(const std::string& root) + { + oracle_.SetLocalOrthanc(root); + } + + void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc) + { + oracle_.SetRemoteOrthanc(orthanc); + } + + void SetDicomCacheSize(size_t size) + { + oracle_.SetDicomCacheSize(size); + } + + WebAssemblyOracle::CachedInstanceAccessor* AccessCachedInstance(const std::string& sopInstanceUid) + { + return new WebAssemblyOracle::CachedInstanceAccessor(oracle_, sopInstanceUid); + } + + virtual ILock* Lock() ORTHANC_OVERRIDE; + }; +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOpenGLContext.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOpenGLContext.cpp Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,202 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#include "WebAssemblyOpenGLContext.h" + +#include "../../../OrthancStone/Sources/StoneException.h" + +#include +#include + +#include +#include + +#include + +namespace OrthancStone +{ + namespace OpenGL + { + class WebAssemblyOpenGLContext::PImpl + { + private: + std::string canvasSelector_; + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context_; + bool isContextLost_; + + public: + explicit PImpl(const std::string& canvasSelector) : + canvasSelector_(canvasSelector), + isContextLost_(false) + { + // Context configuration + EmscriptenWebGLContextAttributes attr; + emscripten_webgl_init_context_attributes(&attr); + + // The next line might be necessary to print using + // WebGL. Sometimes, if set to "false" (the default value), + // the canvas was rendered as a fully white or black + // area. UNCONFIRMED. + attr.preserveDrawingBuffer = true; + + context_ = emscripten_webgl_create_context(canvasSelector.c_str(), &attr); + if (context_ == 0) + { + std::string message("Cannot create an OpenGL context for the element with the following CSS selector: \""); + message += canvasSelector; + message += "\" Please make sure the -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1 flag has been passed to Emscripten when building."; + LOG(ERROR) << message; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, message); + } + } + + void* DebugGetInternalContext() const + { + return reinterpret_cast(context_); + } + + bool IsContextLost() + { + //LOG(TRACE) << "IsContextLost() for context " << std::hex << context_ << std::dec; + bool apiFlag = (emscripten_is_webgl_context_lost(context_) != 0); + isContextLost_ = apiFlag; + return isContextLost_; + } + + void SetLostContext() + { + isContextLost_ = true; + } + + ~PImpl() + { + try + { + EMSCRIPTEN_RESULT result = emscripten_webgl_destroy_context(context_); + if (result != EMSCRIPTEN_RESULT_SUCCESS) + { + LOG(ERROR) << "emscripten_webgl_destroy_context returned code " << result; + } + } + catch (const Orthanc::OrthancException& e) + { + if (e.HasDetails()) + { + LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::~PImpl: " << e.What() << " Details: " << e.GetDetails(); + } + else + { + LOG(ERROR) << "OrthancException in WebAssemblyOpenGLContext::~PImpl: " << e.What(); + } + } + catch (const std::exception& e) + { + LOG(ERROR) << "std::exception in WebAssemblyOpenGLContext::~PImpl: " << e.what(); + } + catch (...) + { + LOG(ERROR) << "Unknown exception in WebAssemblyOpenGLContext::~PImpl"; + } + } + + const std::string& GetCanvasSelector() const + { + return canvasSelector_; + } + + void MakeCurrent() + { + if (IsContextLost()) + { + LOG(ERROR) << "MakeCurrent() called on lost context " << context_; + throw StoneException(ErrorCode_WebGLContextLost); + } + + if (emscripten_is_webgl_context_lost(context_)) + { + LOG(ERROR) << "OpenGL context has been lost for canvas selector: " << canvasSelector_; + SetLostContext(); + throw StoneException(ErrorCode_WebGLContextLost); + } + + if (emscripten_webgl_make_context_current(context_) != EMSCRIPTEN_RESULT_SUCCESS) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Cannot set the OpenGL context"); + } + } + + void SwapBuffer() + { + /** + * "Rendered WebGL content is implicitly presented (displayed to + * the user) on the canvas when the event handler that renders with + * WebGL returns back to the browser event loop." + * https://emscripten.org/docs/api_reference/html5.h.html#webgl-context + * + * Could call "emscripten_webgl_commit_frame()" if + * "explicitSwapControl" option were set to "true". + **/ + } + }; + + + WebAssemblyOpenGLContext::WebAssemblyOpenGLContext(const std::string& canvasSelector) : + pimpl_(new PImpl(canvasSelector)) + { + } + + bool WebAssemblyOpenGLContext::IsContextLost() + { + return pimpl_->IsContextLost(); + } + + void WebAssemblyOpenGLContext::SetLostContext() + { + pimpl_->SetLostContext(); + } + + void* WebAssemblyOpenGLContext::DebugGetInternalContext() const + { + return pimpl_->DebugGetInternalContext(); + } + + void WebAssemblyOpenGLContext::MakeCurrent() + { + assert(pimpl_.get() != NULL); + pimpl_->MakeCurrent(); + } + + void WebAssemblyOpenGLContext::SwapBuffer() + { + assert(pimpl_.get() != NULL); + pimpl_->SwapBuffer(); + } + + const std::string& WebAssemblyOpenGLContext::GetCanvasSelector() const + { + assert(pimpl_.get() != NULL); + return pimpl_->GetCanvasSelector(); + } + } +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOpenGLContext.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOpenGLContext.h Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,82 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#pragma once + +#if !defined(ORTHANC_ENABLE_WASM) +# error Macro ORTHANC_ENABLE_WASM must be defined +#endif + +#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 "../../../OrthancStone/Sources/OpenGL/IOpenGLContext.h" + +#include // For ORTHANC_OVERRIDE + +#include + +namespace OrthancStone +{ + namespace OpenGL + { + class WebAssemblyOpenGLContext : public OpenGL::IOpenGLContext + { + private: + class PImpl; + boost::shared_ptr pimpl_; + + public: + explicit WebAssemblyOpenGLContext(const std::string& canvasSelector); + + virtual bool IsContextLost() ORTHANC_OVERRIDE; + + virtual void MakeCurrent() ORTHANC_OVERRIDE; + + virtual void SwapBuffer() ORTHANC_OVERRIDE; + + /** + Returns true if the underlying context has been successfully recreated + */ + //bool TryRecreate(); + + const std::string& GetCanvasSelector() const; + + + /** + * This is for manual context loss (debug purposes) + **/ + void* DebugGetInternalContext() const; + void SetLostContext(); + }; + } +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.cpp Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,876 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1 +# include "WebAssemblyOracle_Includes.h" +#else +// This is the case when using the WebAssembly side module, and this +// source file must be compiled within the WebAssembly main module +# include +#endif + +#include +#include + +#include +#include +#include + + +#if ORTHANC_ENABLE_DCMTK == 1 +static unsigned int BUCKET_SOP = 1; +#endif + + +namespace OrthancStone +{ + class WebAssemblyOracle::TimeoutContext + { + private: + WebAssemblyOracle& oracle_; + boost::weak_ptr receiver_; + std::unique_ptr command_; + + public: + TimeoutContext(WebAssemblyOracle& oracle, + boost::weak_ptr receiver, + IOracleCommand* command) : + oracle_(oracle), + receiver_(receiver) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + command_.reset(dynamic_cast(command)); + } + } + + void EmitMessage() + { + assert(command_.get() != NULL); + + SleepOracleCommand::TimeoutMessage message(*command_); + oracle_.EmitMessage(receiver_, message); + } + + static void Callback(void *userData) + { + std::unique_ptr context(reinterpret_cast(userData)); + context->EmitMessage(); + } + }; + + + /** + This object is created on the heap for every http request. + It is deleted in the success (or error) callbacks. + + This object references the receiver of the request. Since this is a raw + reference, we need additional checks to make sure we send the response to + the same object, for the object can be deleted and a new one recreated at the + same address (it often happens in the [single-threaded] browser context). + */ + class WebAssemblyOracle::FetchContext : public boost::noncopyable + { + private: + WebAssemblyOracle& oracle_; + boost::weak_ptr receiver_; + std::unique_ptr command_; + std::string expectedContentType_; + + public: + FetchContext(WebAssemblyOracle& oracle, + boost::weak_ptr receiver, + IOracleCommand* command, + const std::string& expectedContentType) : + oracle_(oracle), + receiver_(receiver), + command_(command), + expectedContentType_(expectedContentType) + { + if (Orthanc::Logging::IsTraceLevelEnabled()) + { + // Calling "receiver.lock()" is expensive, hence the quick check if TRACE is enabled + LOG(TRACE) << "WebAssemblyOracle::FetchContext::FetchContext() | " + << "receiver address = " << std::hex << receiver.lock().get(); + } + + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + const std::string& GetExpectedContentType() const + { + return expectedContentType_; + } + + void EmitException(const Orthanc::OrthancException& exception) + { + assert(command_.get() != NULL); + OracleCommandExceptionMessage message(*command_, exception); + oracle_.EmitMessage(receiver_, message); + } + + void ProcessFetchResult(const std::string& answer, + const HttpHeaders& headers) + { + assert(command_.get() != NULL); + oracle_.ProcessFetchResult(receiver_, answer, headers, *command_); + } + + static void SuccessCallback(emscripten_fetch_t *fetch) + { + /** + * Firstly, make a local copy of the fetched information, and + * free data associated with the fetch. + **/ + + if (fetch->userData == NULL) + { + LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback fetch->userData is NULL!"; + return; + } + + std::unique_ptr context(reinterpret_cast(fetch->userData)); + + std::string answer; + if (fetch->numBytes > 0) + { + answer.assign(fetch->data, fetch->numBytes); + } + + + /** + * Retrieving the headers of the HTTP answer. + **/ + HttpHeaders headers; + +#if (__EMSCRIPTEN_major__ < 1 || \ + (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ < 38) || \ + (__EMSCRIPTEN_major__ == 1 && __EMSCRIPTEN_minor__ == 38 && __EMSCRIPTEN_tiny__ < 37)) +# warning Consider upgrading Emscripten to a version above 1.38.37, incomplete support of Fetch API + + /** + * HACK - If emscripten < 1.38.37, the fetch API does not + * contain a way to retrieve the HTTP headers of the answer. We + * make the assumption that the "Content-Type" header of the + * response is the same as the "Accept" header of the + * query. This is fixed thanks to the + * "emscripten_fetch_get_response_headers()" function that was + * added to "fetch.h" at emscripten-1.38.37 on 2019-06-26. + * + * https://github.com/emscripten-core/emscripten/blob/1.38.37/system/include/emscripten/fetch.h + * https://github.com/emscripten-core/emscripten/pull/8486 + **/ + if (fetch->userData != NULL) + { + if (!context->GetExpectedContentType().empty()) + { + headers["Content-Type"] = context->GetExpectedContentType(); + } + } +#else + { + size_t size = emscripten_fetch_get_response_headers_length(fetch); + + std::string plainHeaders(size + 1, '\0'); + emscripten_fetch_get_response_headers(fetch, &plainHeaders[0], size + 1); + + std::vector tokens; + Orthanc::Toolbox::TokenizeString(tokens, plainHeaders, '\n'); + + for (size_t i = 0; i < tokens.size(); i++) + { + size_t p = tokens[i].find(':'); + if (p != std::string::npos) + { + std::string key = Orthanc::Toolbox::StripSpaces(tokens[i].substr(0, p)); + std::string value = Orthanc::Toolbox::StripSpaces(tokens[i].substr(p + 1)); + headers[key] = value; + } + } + } +#endif + + LOG(TRACE) << "About to call emscripten_fetch_close"; + emscripten_fetch_close(fetch); + LOG(TRACE) << "Successfully called emscripten_fetch_close"; + + /** + * Secondly, use the retrieved data. + * IMPORTANT NOTE: the receiver might be dead. This is prevented + * by the object responsible for zombie check, later on. + **/ + try + { + if (context.get() == NULL) + { + LOG(ERROR) << "WebAssemblyOracle::FetchContext::SuccessCallback: (context.get() == NULL)"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + context->ProcessFetchResult(answer, headers); + } + } + catch (Orthanc::OrthancException& e) + { + LOG(INFO) << "Error while processing a fetch answer in the oracle: " << e.What(); + context->EmitException(e); + } + } + + static void FailureCallback(emscripten_fetch_t *fetch) + { + std::unique_ptr context(reinterpret_cast(fetch->userData)); + +#if 0 + { + const size_t kEmscriptenStatusTextSize = sizeof(emscripten_fetch_t::statusText); + char message[kEmscriptenStatusTextSize + 1]; + memcpy(message, fetch->statusText, kEmscriptenStatusTextSize); + message[kEmscriptenStatusTextSize] = 0; + + LOG(ERROR) << "Fetching " << fetch->url + << " failed, HTTP failure status code: " << fetch->status + << " | statusText = " << message + << " | numBytes = " << fetch->numBytes + << " | totalBytes = " << fetch->totalBytes + << " | readyState = " << fetch->readyState; + } +#endif + + context->EmitException(Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol)); + + /** + * TODO - The following code leads to an infinite recursion, at + * least with Firefox running on incognito mode => WHY? + **/ + emscripten_fetch_close(fetch); // Also free data on failure. + } + }; + + + + class WebAssemblyOracle::FetchCommand : public boost::noncopyable + { + private: + WebAssemblyOracle& oracle_; + boost::weak_ptr receiver_; + std::unique_ptr command_; + Orthanc::HttpMethod method_; + std::string url_; + std::string body_; + HttpHeaders headers_; + unsigned int timeout_; + std::string expectedContentType_; + bool hasCredentials_; + std::string username_; + std::string password_; + + public: + FetchCommand(WebAssemblyOracle& oracle, + boost::weak_ptr receiver, + IOracleCommand* command) : + oracle_(oracle), + receiver_(receiver), + command_(command), + method_(Orthanc::HttpMethod_Get), + timeout_(0), + hasCredentials_(false) + { + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + } + + void SetMethod(Orthanc::HttpMethod method) + { + method_ = method; + } + + Orthanc::HttpMethod GetMethod() const + { + return method_; + } + + void SetUrl(const std::string& url) + { + url_ = url; + } + + const std::string& GetUrl() const + { + return url_; + } + + void SetBody(std::string& body /* will be swapped */) + { + body_.swap(body); + } + + void AddHttpHeaders(const HttpHeaders& headers) + { + for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) + { + headers_[it->first] = it->second; + } + } + + const HttpHeaders& GetHttpHeaders() const + { + return headers_; + } + + void SetTimeout(unsigned int timeout) + { + timeout_ = timeout; + } + + unsigned int GetTimeout() const + { + return timeout_; + } + + void SetCredentials(const std::string& username, + const std::string& password) + { + hasCredentials_ = true; + username_ = username; + password_ = password; + } + + void Execute() + { + if (command_.get() == NULL) + { + // Cannot call Execute() twice + LOG(ERROR) << "WebAssemblyOracle::Execute(): (command_.get() == NULL)"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + + const char* method; + + switch (method_) + { + case Orthanc::HttpMethod_Get: + method = "GET"; + break; + + case Orthanc::HttpMethod_Post: + method = "POST"; + break; + + case Orthanc::HttpMethod_Delete: + method = "DELETE"; + break; + + case Orthanc::HttpMethod_Put: + method = "PUT"; + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + strcpy(attr.requestMethod, method); + + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_REPLACE; + attr.onsuccess = FetchContext::SuccessCallback; + attr.onerror = FetchContext::FailureCallback; + attr.timeoutMSecs = timeout_ * 1000; + + if (hasCredentials_) + { + attr.withCredentials = EM_TRUE; + attr.userName = username_.c_str(); + attr.password = password_.c_str(); + } + + std::vector headers; + headers.reserve(2 * headers_.size() + 1); + + std::string expectedContentType; + + for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) + { + std::string key; + Orthanc::Toolbox::ToLowerCase(key, it->first); + + if (key == "accept") + { + expectedContentType = it->second; + } + + if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header + { + headers.push_back(it->first.c_str()); + headers.push_back(it->second.c_str()); + } + } + + headers.push_back(NULL); // Termination of the array of HTTP headers + + attr.requestHeaders = &headers[0]; + + char* requestData = NULL; + if (!body_.empty()) + requestData = reinterpret_cast(malloc(body_.size())); + + try + { + if (!body_.empty()) + { + memcpy(requestData, &(body_[0]), body_.size()); + attr.requestDataSize = body_.size(); + attr.requestData = requestData; + } + attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); + + // Must be the last call to prevent memory leak on error + emscripten_fetch(&attr, url_.c_str()); + } + catch(...) + { + if(requestData != NULL) + free(requestData); + throw; + } + } + }; + + + void WebAssemblyOracle::ProcessFetchResult(boost::weak_ptr& receiver, + const std::string& answer, + const HttpHeaders& headers, + const IOracleCommand& command) + { + switch (command.GetType()) + { + case IOracleCommand::Type_Http: + { + HttpCommand::SuccessMessage message(dynamic_cast(command), headers, answer); + EmitMessage(receiver, message); + break; + } + + case IOracleCommand::Type_OrthancRestApi: + { + LOG(TRACE) << "WebAssemblyOracle::FetchContext::SuccessCallback. About to call EmitMessage(message);"; + OrthancRestApiCommand::SuccessMessage message + (dynamic_cast(command), headers, answer); + EmitMessage(receiver, message); + break; + } + + case IOracleCommand::Type_GetOrthancImage: + { + dynamic_cast(command).ProcessHttpAnswer(receiver, *this, answer, headers); + break; + } + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + { + dynamic_cast(command).ProcessHttpAnswer(receiver, *this, answer); + break; + } + + case IOracleCommand::Type_ParseDicomFromWado: + { +#if ORTHANC_ENABLE_DCMTK == 1 + const ParseDicomFromWadoCommand& c = dynamic_cast(command); + + size_t fileSize; + std::unique_ptr dicom + (ParseDicomSuccessMessage::ParseWadoAnswer(fileSize, answer, headers)); + + { + ParseDicomSuccessMessage message(c, c.GetSource(), *dicom, fileSize, true); + EmitMessage(receiver, message); + } + + if (dicomCache_.get()) + { + // Store it into the cache for future use + dicomCache_->Acquire(BUCKET_SOP, c.GetSopInstanceUid(), dicom.release(), fileSize, true); + } +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); +#endif + break; + } + + default: + LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in SuccessCallback): " + << command.GetType(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + void WebAssemblyOracle::SetOrthancUrl(FetchCommand& command, + const std::string& uri) const + { + if (isLocalOrthanc_) + { + command.SetUrl(localOrthancRoot_ + uri); + } + else + { + command.SetUrl(remoteOrthanc_.GetUrl() + uri); + command.AddHttpHeaders(remoteOrthanc_.GetHttpHeaders()); + + if (!remoteOrthanc_.GetUsername().empty()) + { + command.SetCredentials(remoteOrthanc_.GetUsername(), remoteOrthanc_.GetPassword()); + } + } + } + + + void WebAssemblyOracle::Execute(boost::weak_ptr receiver, + HttpCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + fetch.SetMethod(command->GetMethod()); + fetch.SetUrl(command->GetUrl()); + fetch.AddHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + if (command->GetMethod() == Orthanc::HttpMethod_Post || + command->GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body; + command->SwapBody(body); + fetch.SetBody(body); + } + + fetch.Execute(); + } + + + void WebAssemblyOracle::Execute(boost::weak_ptr receiver, + OrthancRestApiCommand* command) + { + try + { + //LOG(TRACE) << "*********** WebAssemblyOracle::Execute."; + //LOG(TRACE) << "WebAssemblyOracle::Execute | command = " << command; + FetchCommand fetch(*this, receiver, command); + + fetch.SetMethod(command->GetMethod()); + SetOrthancUrl(fetch, command->GetUri()); + fetch.AddHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + if (command->GetMethod() == Orthanc::HttpMethod_Post || + command->GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body; + command->SwapBody(body); + fetch.SetBody(body); + } + + fetch.Execute(); + //LOG(TRACE) << "*********** successful end of WebAssemblyOracle::Execute."; + } + catch (const Orthanc::OrthancException& e) + { + if (e.HasDetails()) + { + LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What() << " Details: " << e.GetDetails(); + } + else + { + LOG(ERROR) << "OrthancException in WebAssemblyOracle::Execute: " << e.What(); + } + //LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute."; + throw; + } + catch (const std::exception& e) + { + LOG(ERROR) << "std::exception in WebAssemblyOracle::Execute: " << e.what(); +// LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute."; + throw; + } + catch (...) + { + LOG(ERROR) << "Unknown exception in WebAssemblyOracle::Execute"; +// LOG(TRACE) << "*********** failing end of WebAssemblyOracle::Execute."; + throw; + } + } + + + void WebAssemblyOracle::Execute(boost::weak_ptr receiver, + GetOrthancImageCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + SetOrthancUrl(fetch, command->GetUri()); + fetch.AddHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + fetch.Execute(); + } + + + void WebAssemblyOracle::Execute(boost::weak_ptr receiver, + GetOrthancWebViewerJpegCommand* command) + { + FetchCommand fetch(*this, receiver, command); + + SetOrthancUrl(fetch, command->GetUri()); + fetch.AddHttpHeaders(command->GetHttpHeaders()); + fetch.SetTimeout(command->GetTimeout()); + + fetch.Execute(); + } + + + void WebAssemblyOracle::Execute(boost::weak_ptr receiver, + ParseDicomFromWadoCommand* command) + { + std::unique_ptr protection(command); + +#if ORTHANC_ENABLE_DCMTK == 1 + if (dicomCache_.get()) + { + ParsedDicomCache::Reader reader(*dicomCache_, BUCKET_SOP, protection->GetSopInstanceUid()); + if (reader.IsValid() && + reader.HasPixelData()) + { + // Reuse the DICOM file from the cache + ParseDicomSuccessMessage message(*protection, protection->GetSource(), reader.GetDicom(), + reader.GetFileSize(), reader.HasPixelData()); + EmitMessage(receiver, message); + return; + } + } +#endif + + switch (command->GetRestCommand().GetType()) + { + case IOracleCommand::Type_Http: + { + const HttpCommand& rest = + dynamic_cast(protection->GetRestCommand()); + + FetchCommand fetch(*this, receiver, protection.release()); + + fetch.SetMethod(rest.GetMethod()); + fetch.SetUrl(rest.GetUrl()); + fetch.AddHttpHeaders(rest.GetHttpHeaders()); + fetch.SetTimeout(rest.GetTimeout()); + + if (rest.GetMethod() == Orthanc::HttpMethod_Post || + rest.GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body = rest.GetBody(); + fetch.SetBody(body); + } + + fetch.Execute(); + break; + } + + case IOracleCommand::Type_OrthancRestApi: + { + const OrthancRestApiCommand& rest = + dynamic_cast(protection->GetRestCommand()); + + FetchCommand fetch(*this, receiver, protection.release()); + + fetch.SetMethod(rest.GetMethod()); + SetOrthancUrl(fetch, rest.GetUri()); + fetch.AddHttpHeaders(rest.GetHttpHeaders()); + fetch.SetTimeout(rest.GetTimeout()); + + if (rest.GetMethod() == Orthanc::HttpMethod_Post || + rest.GetMethod() == Orthanc::HttpMethod_Put) + { + std::string body = rest.GetBody(); + fetch.SetBody(body); + } + + fetch.Execute(); + break; + } + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + + + bool WebAssemblyOracle::Schedule(boost::shared_ptr receiver, + IOracleCommand* command) + { + LOG(TRACE) << "WebAssemblyOracle::Schedule : receiver = " + << std::hex << receiver.get(); + + std::unique_ptr protection(command); + + if (command == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + + switch (command->GetType()) + { + case IOracleCommand::Type_Http: + Execute(receiver, dynamic_cast(protection.release())); + break; + + case IOracleCommand::Type_OrthancRestApi: + Execute(receiver, dynamic_cast(protection.release())); + break; + + case IOracleCommand::Type_GetOrthancImage: + Execute(receiver, dynamic_cast(protection.release())); + break; + + case IOracleCommand::Type_GetOrthancWebViewerJpeg: + Execute(receiver, dynamic_cast(protection.release())); + break; + + case IOracleCommand::Type_Sleep: + { + unsigned int timeoutMS = dynamic_cast(command)->GetDelay(); + emscripten_set_timeout(TimeoutContext::Callback, timeoutMS, + new TimeoutContext(*this, receiver, protection.release())); + break; + } + + case IOracleCommand::Type_ParseDicomFromWado: +#if ORTHANC_ENABLE_DCMTK == 1 + Execute(receiver, dynamic_cast(protection.release())); +#else + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented, + "DCMTK must be enabled to parse DICOM files"); +#endif + break; + + default: + LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle (in Schedule): " + << command->GetType(); + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + + return true; + } + + + void WebAssemblyOracle::SetDicomCacheSize(size_t size) + { +#if ORTHANC_ENABLE_DCMTK == 1 + if (size == 0) + { + dicomCache_.reset(); + } + else + { + dicomCache_.reset(new ParsedDicomCache(size)); + } +#else + LOG(INFO) << "DCMTK support is disabled, the DICOM cache is disabled"; +#endif + } + + + WebAssemblyOracle::CachedInstanceAccessor::CachedInstanceAccessor(WebAssemblyOracle& oracle, + const std::string& sopInstanceUid) + { +#if ORTHANC_ENABLE_DCMTK == 1 + if (oracle.dicomCache_.get() != NULL) + { + reader_.reset(new ParsedDicomCache::Reader(*oracle.dicomCache_, BUCKET_SOP, sopInstanceUid)); + } +#endif + } + + bool WebAssemblyOracle::CachedInstanceAccessor::IsValid() const + { +#if ORTHANC_ENABLE_DCMTK == 1 + return (reader_.get() != NULL && + reader_->IsValid()); +#else + return false; +#endif + } + +#if ORTHANC_ENABLE_DCMTK == 1 + const Orthanc::ParsedDicomFile& WebAssemblyOracle::CachedInstanceAccessor::GetDicom() const + { + if (IsValid()) + { + assert(reader_.get() != NULL); + return reader_->GetDicom(); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } +#endif + + size_t WebAssemblyOracle::CachedInstanceAccessor::GetFileSize() const + { +#if ORTHANC_ENABLE_DCMTK == 1 + if (IsValid()) + { + assert(reader_.get() != NULL); + return reader_->GetFileSize(); + } + else +#endif + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + bool WebAssemblyOracle::CachedInstanceAccessor::HasPixelData() const + { +#if ORTHANC_ENABLE_DCMTK == 1 + if (IsValid()) + { + assert(reader_.get() != NULL); + return reader_->HasPixelData(); + } + else +#endif + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle.h Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,156 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#pragma once + +#include "../../../OrthancStone/Sources/OrthancStone.h" + +#if !defined(ORTHANC_ENABLE_WASM) +# error The macro ORTHANC_ENABLE_WASM must be defined +#endif + +#if ORTHANC_ENABLE_WASM != 1 +# error This file can only compiled for WebAssembly +#endif + +#include "../../../OrthancStone/Sources/Messages/IObservable.h" +#include "../../../OrthancStone/Sources/Messages/IMessageEmitter.h" +#include "../../../OrthancStone/Sources/Oracle/IOracle.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +# include "../../../OrthancStone/Sources/Toolbox/ParsedDicomCache.h" +#endif + +#include // For ORTHANC_OVERRIDE +#include + +#include + +namespace OrthancStone +{ + class GetOrthancImageCommand; + class GetOrthancWebViewerJpegCommand; + class HttpCommand; + class OrthancRestApiCommand; + class ParseDicomFromWadoCommand; + + class WebAssemblyOracle : + public IOracle, + public IMessageEmitter + { + private: + typedef std::map HttpHeaders; + + class TimeoutContext; + class FetchContext; + class FetchCommand; + + void SetOrthancUrl(FetchCommand& command, + const std::string& uri) const; + + void Execute(boost::weak_ptr receiver, + HttpCommand* command); + + void Execute(boost::weak_ptr receiver, + OrthancRestApiCommand* command); + + void Execute(boost::weak_ptr receiver, + GetOrthancImageCommand* command); + + void Execute(boost::weak_ptr receiver, + GetOrthancWebViewerJpegCommand* command); + + void Execute(boost::weak_ptr receiver, + ParseDicomFromWadoCommand* command); + + IObservable oracleObservable_; + bool isLocalOrthanc_; + std::string localOrthancRoot_; + Orthanc::WebServiceParameters remoteOrthanc_; + +#if ORTHANC_ENABLE_DCMTK == 1 + std::unique_ptr dicomCache_; +#endif + + void ProcessFetchResult(boost::weak_ptr& receiver, + const std::string& answer, + const HttpHeaders& headers, + const IOracleCommand& command); + + public: + WebAssemblyOracle() : + isLocalOrthanc_(false) + { + } + + virtual void EmitMessage(boost::weak_ptr observer, + const IMessage& message) ORTHANC_OVERRIDE + { + oracleObservable_.EmitMessage(observer, message); + } + + virtual bool Schedule(boost::shared_ptr receiver, + IOracleCommand* command) ORTHANC_OVERRIDE; + + IObservable& GetOracleObservable() + { + return oracleObservable_; + } + + void SetLocalOrthanc(const std::string& root) + { + isLocalOrthanc_ = true; + localOrthancRoot_ = root; + } + + void SetRemoteOrthanc(const Orthanc::WebServiceParameters& orthanc) + { + isLocalOrthanc_ = false; + remoteOrthanc_ = orthanc; + } + + void SetDicomCacheSize(size_t size); + + class CachedInstanceAccessor : public boost::noncopyable + { + private: +#if ORTHANC_ENABLE_DCMTK == 1 + std::unique_ptr reader_; +#endif + + public: + CachedInstanceAccessor(WebAssemblyOracle& oracle, + const std::string& sopInstanceUid); + + bool IsValid() const; + +#if ORTHANC_ENABLE_DCMTK == 1 + const Orthanc::ParsedDicomFile& GetDicom() const; +#endif + + size_t GetFileSize() const; + + bool HasPixelData() const; + }; + }; +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle_Includes.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyOracle_Includes.h Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,44 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#pragma once + +/** + * This file serves as an indirection to avoid large "#if + * ORTHANC_BUILDING_STONE_LIBRARY == 1" in "WebAssemblyOracle.cpp" + **/ + +#include "WebAssemblyOracle.h" + +#include "../../../OrthancStone/Sources/Oracle/OracleCommandExceptionMessage.h" + +#if ORTHANC_ENABLE_DCMTK == 1 +# include "../../../OrthancStone/Sources/Oracle/ParseDicomSuccessMessage.h" +#endif + +#include "../../../OrthancStone/Sources/Oracle/GetOrthancImageCommand.h" +#include "../../../OrthancStone/Sources/Oracle/GetOrthancWebViewerJpegCommand.h" +#include "../../../OrthancStone/Sources/Oracle/HttpCommand.h" +#include "../../../OrthancStone/Sources/Oracle/OrthancRestApiCommand.h" +#include "../../../OrthancStone/Sources/Oracle/ParseDicomFromWadoCommand.h" +#include "../../../OrthancStone/Sources/Oracle/SleepOracleCommand.h" diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyViewport.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyViewport.cpp Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,474 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#if defined(ORTHANC_BUILDING_STONE_LIBRARY) && ORTHANC_BUILDING_STONE_LIBRARY == 1 +# include "WebAssemblyViewport.h" +# include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" +# include "../../../OrthancStone/Sources/Toolbox/GenericToolbox.h" +# include "../../../OrthancStone/Sources/Viewport/DefaultViewportInteractor.h" +#else +// This is the case when using the WebAssembly side module, and this +// source file must be compiled within the WebAssembly main module +# include +# include +# include +# include +#endif + + +#include + +#include +#include +#include + +namespace OrthancStone +{ + static void ConvertMouseEvent(PointerEvent& target, + const EmscriptenMouseEvent& source, + const ICompositor& compositor) + { + int x = static_cast(source.targetX); + int y = static_cast(source.targetY); + + switch (source.button) + { + case 0: + target.SetMouseButton(MouseButton_Left); + break; + + case 1: + target.SetMouseButton(MouseButton_Middle); + break; + + case 2: + target.SetMouseButton(MouseButton_Right); + break; + + default: + target.SetMouseButton(MouseButton_None); + break; + } + + target.AddPosition(compositor.GetPixelCenterCoordinates(x, y)); + target.SetAltModifier(source.altKey); + target.SetControlModifier(source.ctrlKey); + target.SetShiftModifier(source.shiftKey); + } + + + class WebAssemblyViewport::WasmLock : public ILock + { + private: + WebAssemblyViewport& that_; + + public: + WasmLock(WebAssemblyViewport& that) : + that_(that) + { + } + + virtual bool HasCompositor() const ORTHANC_OVERRIDE + { + return that_.compositor_.get() != NULL; + } + + virtual ICompositor& GetCompositor() ORTHANC_OVERRIDE + { + if (that_.compositor_.get() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + return *that_.compositor_; + } + } + + virtual ViewportController& GetController() ORTHANC_OVERRIDE + { + assert(that_.controller_); + return *that_.controller_; + } + + virtual void Invalidate() ORTHANC_OVERRIDE + { + that_.Invalidate(); + } + + virtual void RefreshCanvasSize() ORTHANC_OVERRIDE + { + that_.RefreshCanvasSize(); + } + }; + + EM_BOOL WebAssemblyViewport::OnRequestAnimationFrame(double time, void *userData) + { + LOG(TRACE) << __func__; + + WebAssemblyViewport* that = + WebAssemblyViewport::DereferenceObjectCookie(userData); + + if (that != NULL) + { + if (that->compositor_.get() != NULL && + that->controller_ /* should always be true */) + { + that->Paint(*that->compositor_, *that->controller_); + } + } + else + { + LOG(TRACE) << "WebAssemblyViewport::OnRequestAnimationFrame: the " << + "WebAssemblyViewport is deleted and Paint will not be called."; + } + WebAssemblyViewport::ReleaseObjectCookie(userData); + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + EM_BOOL WebAssemblyViewport::OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) + { + LOG(TRACE) << __func__; + WebAssemblyViewport* that = reinterpret_cast(userData); + + if (that->compositor_.get() != NULL) + { + that->RefreshCanvasSize(); + that->Invalidate(); + } + + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + + EM_BOOL WebAssemblyViewport::OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) + { + WebAssemblyViewport* that = reinterpret_cast(userData); + + LOG(TRACE) << "mouse down: " << that->GetCanvasCssSelector(); + + if (that->compositor_.get() != NULL && + that->interactor_.get() != NULL) + { + PointerEvent pointer; + ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); + + that->controller_->HandleMousePress(*that->interactor_, pointer, + that->compositor_->GetCanvasWidth(), + that->compositor_->GetCanvasHeight()); + that->Invalidate(); + } + + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + + EM_BOOL WebAssemblyViewport::OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) + { + WebAssemblyViewport* that = reinterpret_cast(userData); + + if (that->compositor_.get() != NULL) + { + if (that->controller_->HasActiveTracker()) + { + PointerEvent pointer; + ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); + + if (that->controller_->HandleMouseMove(pointer)) + { + that->Invalidate(); + } + } + else if (that->interactor_.get() != NULL && + that->interactor_->HasMouseHover()) + { + // New in Stone Web viewer 2.0 + PointerEvent pointer; + ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); + that->interactor_->HandleMouseHover(*that, pointer); + } + } + + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + EM_BOOL WebAssemblyViewport::OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) + { + LOG(TRACE) << __func__; + WebAssemblyViewport* that = reinterpret_cast(userData); + + if (that->compositor_.get() != NULL) + { + PointerEvent pointer; + ConvertMouseEvent(pointer, *mouseEvent, *that->compositor_); + that->controller_->HandleMouseRelease(pointer); + that->Invalidate(); + } + + LOG(TRACE) << "Exiting: " << __func__; + return true; + } + + void* WebAssemblyViewport::CreateObjectCookie() + { + boost::weak_ptr* weakThisPtr = + new boost::weak_ptr(); + + *weakThisPtr = shared_from_this(); + + void* cookie = reinterpret_cast(weakThisPtr); + + LOG(TRACE) << "WebAssemblyViewport::CreateObjectCookie() => cookie = " + << cookie << "\n"; + + return cookie; + } + + WebAssemblyViewport* WebAssemblyViewport::DereferenceObjectCookie(void* cookie) + { + LOG(TRACE) << "WebAssemblyViewport::DereferenceObjectCookie(cookie = " + << cookie << ")\n"; + + boost::weak_ptr* weakThisPtr = + reinterpret_cast*>(cookie); + + boost::shared_ptr sharedThis = weakThisPtr->lock(); + + return sharedThis.get(); + } + + void WebAssemblyViewport::ReleaseObjectCookie(void* cookie) + { + LOG(TRACE) << "WebAssemblyViewport::ReleaseObjectCookie(cookie = " + << cookie << ")\n"; + + boost::weak_ptr* weakThisPtr = + reinterpret_cast*>(cookie); + + delete weakThisPtr; + } + + void WebAssemblyViewport::Invalidate() + { + LOG(TRACE) << "WebAssemblyViewport::Invalidate()\n"; + long id = emscripten_request_animation_frame(OnRequestAnimationFrame, + CreateObjectCookie()); + //animationFrameCallbackIds_.push_back(id); + } + + void WebAssemblyViewport::FitForPrint() + { + if (compositor_.get() != NULL && + controller_ /* should always be true */) + { + RefreshCanvasSize(); + compositor_->FitContent(controller_->GetScene()); + + void* cookie = CreateObjectCookie(); + OnRequestAnimationFrame(0, cookie); // Mandatory to work with Firefox + } + } + + void WebAssemblyViewport::AcquireCompositor(ICompositor* compositor /* takes ownership */) + { + if (compositor == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + compositor_.reset(compositor); + } + } + +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1 +// everything OK..... we're using the new setting +#else +#pragma message("WARNING: DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR is not defined or equal to 0. Stone will use the OLD Emscripten rules for DOM element selection.") +#endif + + WebAssemblyViewport::WebAssemblyViewport( + const std::string& canvasId, bool enableEmscriptenMouseEvents) : + canvasId_(canvasId), +#if DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR == 1 + canvasCssSelector_("#" + canvasId), +#else + canvasCssSelector_(canvasId), +#endif + interactor_(new DefaultViewportInteractor), + enableEmscriptenMouseEvents_(enableEmscriptenMouseEvents), + canvasWidth_(0), + canvasHeight_(0) + { + } + + void WebAssemblyViewport::PostConstructor() + { + boost::shared_ptr viewport = shared_from_this(); + controller_.reset(new ViewportController(viewport)); + + LOG(INFO) << "Initializing Stone viewport on HTML canvas: " + << canvasId_; + + if (canvasId_.empty() || + canvasId_[0] == '#') + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "The canvas identifier must not start with '#'"); + } + + // Disable right-click on the canvas (i.e. context menu) + EM_ASM({ + document.getElementById(UTF8ToString($0)).oncontextmenu = + function(event) + { + event.preventDefault(); + } + }, + canvasId_.c_str() // $0 + ); + + // It is not possible to monitor the resizing of individual + // canvas, so we track the full window of the browser + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, + reinterpret_cast(this), + false, + OnResize); + + if (enableEmscriptenMouseEvents_) + { + + // if any of this function causes an error in the console, please + // make sure you are using the new (as of 1.39.x) version of + // emscripten element lookup rules( pass + // "-s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=1" to the linker. + + emscripten_set_mousedown_callback(canvasCssSelector_.c_str(), + reinterpret_cast(this), + false, + OnMouseDown); + + emscripten_set_mousemove_callback(canvasCssSelector_.c_str(), + reinterpret_cast(this), + false, + OnMouseMove); + + emscripten_set_mouseup_callback(canvasCssSelector_.c_str(), + reinterpret_cast(this), + false, + OnMouseUp); + } + } + + WebAssemblyViewport::~WebAssemblyViewport() + { + LOG(TRACE) << "WebAssemblyViewport::~WebAssemblyViewport()\n"; + + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, + reinterpret_cast(this), + false, + NULL); + + if (enableEmscriptenMouseEvents_) + { + + emscripten_set_mousedown_callback(canvasCssSelector_.c_str(), + reinterpret_cast(this), + false, + NULL); + + emscripten_set_mousemove_callback(canvasCssSelector_.c_str(), + reinterpret_cast(this), + false, + NULL); + + emscripten_set_mouseup_callback(canvasCssSelector_.c_str(), + reinterpret_cast(this), + false, + NULL); + } + } + + IViewport::ILock* WebAssemblyViewport::Lock() + { + return new WasmLock(*this); + } + + void WebAssemblyViewport::AcquireInteractor(IViewportInteractor* interactor) + { + if (interactor == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); + } + else + { + interactor_.reset(interactor); + } + } + + + void WebAssemblyViewport::RefreshCanvasSize() + { + double w = -1, h = -1; + EMSCRIPTEN_RESULT result = + emscripten_get_element_css_size(GetCanvasCssSelector().c_str(), &w, &h); + + if (result != EMSCRIPTEN_RESULT_SUCCESS) + { + LOG(WARNING) << "WebAssemblyViewport::RefreshCanvasSize failed to " + << "retrieve CSS size for " << GetCanvasCssSelector(); + } + + /** + * Emscripten has the function emscripten_get_element_css_size() + * to query the width and height of a named HTML element. I'm + * calling this first to get the initial size of the canvas DOM + * element, and then call emscripten_set_canvas_size() to + * initialize the framebuffer size of the canvas to the same + * size as its DOM element. + * https://floooh.github.io/2017/02/22/emsc-html.html + **/ + if (w > 0 && + h > 0) + { + canvasWidth_ = static_cast(boost::math::iround(w)); + canvasHeight_ = static_cast(boost::math::iround(h)); + } + else + { + canvasWidth_ = 0; + canvasHeight_ = 0; + } + + emscripten_set_canvas_element_size(GetCanvasCssSelector().c_str(), canvasWidth_, canvasHeight_); + + if (compositor_.get() != NULL) + { + compositor_->SetCanvasSize(canvasWidth_, canvasHeight_); + } + } +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyViewport.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebAssemblyViewport.h Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,181 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#pragma once + +#include "../../../OrthancStone/Sources/OrthancStone.h" + +#if !defined(ORTHANC_ENABLE_WASM) +# error Macro ORTHANC_ENABLE_WASM must be defined +#endif + +#if ORTHANC_ENABLE_WASM != 1 +# error This file can only be used if targeting WebAssembly +#endif + +#include "../../../OrthancStone/Sources/Viewport/IViewport.h" +#include "../../../OrthancStone/Sources/Viewport/IViewportInteractor.h" + +#include + +#include +#include + +#include +#include +#include +#include + +namespace OrthancStone +{ + class WebAssemblyViewport : public IViewport, + public boost::enable_shared_from_this + + { + private: + class WasmLock; + + std::string canvasId_; + std::string canvasCssSelector_; + std::unique_ptr compositor_; + std::unique_ptr controller_; + std::unique_ptr interactor_; + bool enableEmscriptenMouseEvents_; + unsigned int canvasWidth_; + unsigned int canvasHeight_; + + static EM_BOOL OnRequestAnimationFrame(double time, void *userData); + + static EM_BOOL OnResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData); + + static EM_BOOL OnMouseDown(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + + static EM_BOOL OnMouseMove(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + + static EM_BOOL OnMouseUp(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + + protected: + void Invalidate(); + + void ClearCompositor() + { + compositor_.reset(); + } + + bool HasCompositor() const + { + return compositor_.get() != NULL; + } + + void AcquireCompositor(ICompositor* compositor /* takes ownership */); + + virtual void Paint(ICompositor& compositor, + ViewportController& controller) = 0; + + /** + The second argument is temporary and should be deleted once the migration + to interactors is finished. It should be set to "true" for new applications. + */ + WebAssemblyViewport(const std::string& canvasId, + bool enableEmscriptenMouseEvents); + + void PostConstructor(); + + + /** + * This method can be called to retrieve a cookie that can be passed to + * C-style callbacks that expect the object to be passed as a void* + * + * This cookie is a resource and must be freed when it is guaranteed + * not to be used anymore, with ReleaseObjectCookie + */ + void* CreateObjectCookie(); + + /** + * This static method can be used to dereference a cookie (i.e. retrieve + * a pointer to the underlying object) that has been created with + * WebAssemblyViewport::CreateObjectCookie() + * + * If this method returns NULL, it basically means that the + * WebAssemblyViewport has already been deleted and that you should NOT + * attempt to use it! + * + * This method never releases the cookie (for other in-flight callbacks + * could possibly require to cookie to be valid) + * + * If this method is called AFTER ReleaseObjectCookie has been called on + * the same cookie, the behavior is undefined and things will CERTAINLY + * go wrong. + * + * NEVER call this method on a void* that has not been generated by the + * CreateObjectCookie method of this class + */ + static WebAssemblyViewport* DereferenceObjectCookie(void* cookie); + + /** + * This method must be used when the object cookie will not be used anymore. + * + * You must call it when you are certain that no entity will attempt to + * dereference the cookie. + */ + static void ReleaseObjectCookie(void* cookie); + + void RefreshCanvasSize(); + + unsigned int GetCanvasWidth() const + { + return canvasWidth_; + } + + unsigned int GetCanvasHeight() + { + return canvasHeight_; + } + + public: + virtual ILock* Lock() ORTHANC_OVERRIDE; + + ~WebAssemblyViewport(); + + /** + This method takes ownership + */ + void AcquireInteractor(IViewportInteractor* interactor); + + const std::string& GetCanvasId() const + { + return canvasId_; + } + + /** + emscripten functions requires the css selector for the canvas. This is + different from the canvas id (the syntax is '#mycanvasid') + */ + const std::string& GetCanvasCssSelector() const + { + return canvasCssSelector_; + } + + void FitForPrint(); + }; +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebGLViewport.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebGLViewport.cpp Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,88 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#include "WebGLViewport.h" + +#include "../../../OrthancStone/Sources/StoneException.h" +#include "../../../OrthancStone/Sources/Scene2D/OpenGLCompositor.h" +#include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" + +namespace OrthancStone +{ + void WebGLViewport::Paint(ICompositor& compositor, + ViewportController& controller) + { + try + { + compositor.Refresh(controller.GetScene()); + + /** + * No need to manually swap the buffer: "Rendered WebGL content + * is implicitly presented (displayed to the user) on the canvas + * when the event handler that renders with WebGL returns back + * to the browser event loop." + * https://emscripten.org/docs/api_reference/html5.h.html#webgl-context + * + * Could call "emscripten_webgl_commit_frame()" if + * "explicitSwapControl" option were set to "true". + **/ + } + catch (const StoneException& e) + { + // Ignore problems about the loss of the WebGL context (edge case) + if (e.GetErrorCode() == ErrorCode_WebGLContextLost) + { + return; + } + else + { + throw; + } + } + } + + + WebGLViewport::WebGLViewport(const std::string& canvasId, bool enableEmscriptenMouseEvents) : + WebAssemblyViewport(canvasId,enableEmscriptenMouseEvents), + context_(GetCanvasCssSelector()) + { + AcquireCompositor(new OpenGLCompositor(context_)); + } + + boost::shared_ptr WebGLViewport::Create( + const std::string& canvasId, bool enableEmscriptenMouseEvents) + { + boost::shared_ptr that = boost::shared_ptr( + new WebGLViewport(canvasId, enableEmscriptenMouseEvents)); + + that->WebAssemblyViewport::PostConstructor(); + return that; + } + + WebGLViewport::~WebGLViewport() + { + // Make sure to delete the compositor before its parent "context_" gets + // deleted + ClearCompositor(); + } +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebGLViewport.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebGLViewport.h Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,54 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#pragma once + +#include "WebAssemblyOpenGLContext.h" +#include "WebAssemblyViewport.h" + +namespace OrthancStone +{ + class WebGLViewport : public WebAssemblyViewport + { + private: + OpenGL::WebAssemblyOpenGLContext context_; + + WebGLViewport(const std::string& canvasId, + bool enableEmscriptenMouseEvents); + + protected: + virtual void Paint(ICompositor& compositor, + ViewportController& controller) ORTHANC_OVERRIDE; + + public: + static boost::shared_ptr Create(const std::string& canvasId, + bool enableEmscriptenMouseEvents = true); + + virtual ~WebGLViewport(); + + bool IsContextLost() + { + return context_.IsContextLost(); + } + }; +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebGLViewportsRegistry.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebGLViewportsRegistry.cpp Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,223 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#include "WebGLViewportsRegistry.h" + +#include "../../../OrthancStone/Sources/Scene2DViewport/ViewportController.h" +#include "../../../OrthancStone/Sources/Toolbox/GenericToolbox.h" + +#include + +#include + + +static double viewportsTimeout_ = 1000; +static std::unique_ptr globalRegistry_; + + +namespace OrthancStone +{ + void WebGLViewportsRegistry::LaunchTimer() + { + timeOutID_ = emscripten_set_timeout( + OnTimeoutCallback, + timeoutMS_, + reinterpret_cast(this)); + } + + void WebGLViewportsRegistry::OnTimeout() + { + for (Viewports::iterator it = viewports_.begin(); + it != viewports_.end(); + ++it) + { + if (it->second == NULL || + it->second->IsContextLost()) + { + LOG(INFO) << "WebGL context lost for canvas: " << it->first; + + // Try and duplicate the HTML5 canvas in the DOM + EM_ASM({ + var canvas = document.getElementById(UTF8ToString($0)); + if (canvas) { + var parent = canvas.parentElement; + if (parent) { + var cloned = canvas.cloneNode(true /* deep copy */); + parent.insertBefore(cloned, canvas); + parent.removeChild(canvas); + } + } + }, + it->first.c_str() // $0 = ID of the canvas + ); + + // At this point, the old canvas is removed from the DOM and + // replaced by a fresh one with the same ID: Recreate the + // WebGL context on the new canvas + boost::shared_ptr viewport; + + // we need to steal the properties from the old viewport + // and set them to the new viewport + { + std::unique_ptr lock(it->second->Lock()); + + // TODO: remove ViewportController + Scene2D* scene = lock->GetController().ReleaseScene(); + viewport = WebGLViewport::Create(it->first); + + { + std::unique_ptr newLock(viewport->Lock()); + newLock->GetController().AcquireScene(scene); + } + } + + // Replace the old WebGL viewport by the new one + it->second = viewport; + + // Tag the fresh canvas as needing a repaint + { + std::unique_ptr lock(it->second->Lock()); + lock->Invalidate(); + } + } + } + + LaunchTimer(); + } + + void WebGLViewportsRegistry::OnTimeoutCallback(void *userData) + { + // This object dies with the process or tab. + WebGLViewportsRegistry* that = + reinterpret_cast(userData); + that->OnTimeout(); + } + + WebGLViewportsRegistry::WebGLViewportsRegistry(double timeoutMS) : + timeoutMS_(timeoutMS), + timeOutID_(0) + { + if (timeoutMS <= 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + LaunchTimer(); + } + + WebGLViewportsRegistry::~WebGLViewportsRegistry() + { + emscripten_clear_timeout(timeOutID_); + Clear(); + } + + boost::shared_ptr WebGLViewportsRegistry::Add( + const std::string& canvasId) + { + if (viewports_.find(canvasId) != viewports_.end()) + { + LOG(ERROR) << "Canvas was already registered: " << canvasId; + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + boost::shared_ptr viewport = + WebGLViewport::Create(canvasId); + viewports_[canvasId] = viewport; + return viewport; + } + } + + void WebGLViewportsRegistry::Remove(const std::string& canvasId) + { + Viewports::iterator found = viewports_.find(canvasId); + + if (found == viewports_.end()) + { + LOG(ERROR) << "Cannot remove unregistered canvas: " << canvasId; + } + else + { + viewports_.erase(found); + } + } + + void WebGLViewportsRegistry::Clear() + { + viewports_.clear(); + } + + WebGLViewportsRegistry::Accessor::Accessor(WebGLViewportsRegistry& that, + const std::string& canvasId) : + that_(that) + { + Viewports::iterator viewport = that.viewports_.find(canvasId); + if (viewport != that.viewports_.end() && + viewport->second != NULL) + { + lock_.reset(viewport->second->Lock()); + } + } + + IViewport::ILock& WebGLViewportsRegistry::Accessor::GetViewport() const + { + if (IsValid()) + { + return *lock_; + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + } + + + void WebGLViewportsRegistry::FinalizeGlobalRegistry() + { + globalRegistry_.reset(); + } + + + void WebGLViewportsRegistry::SetGlobalRegistryTimeout(double timeout) + { + if (globalRegistry_.get()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + else + { + viewportsTimeout_ = timeout; + } + } + + + WebGLViewportsRegistry& WebGLViewportsRegistry::GetGlobalRegistry() + { + if (globalRegistry_.get() == NULL) + { + globalRegistry_.reset(new WebGLViewportsRegistry(viewportsTimeout_)); + } + + return *globalRegistry_; + } +} diff -r a5e54bd87b25 -r 917500c46fe0 OrthancStone/Sources/Platforms/WebAssembly/WebGLViewportsRegistry.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/Platforms/WebAssembly/WebGLViewportsRegistry.h Sat Jan 29 12:47:32 2022 +0100 @@ -0,0 +1,93 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2022 Osimis S.A., Belgium + * Copyright (C) 2021-2022 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * . + **/ + + +#pragma once + +#include "WebGLViewport.h" + +#include + +namespace OrthancStone +{ + /** + * This singleton class must be used if many WebGL viewports are + * created by the higher-level application, implying possible loss + * of WebGL contexts. The object will run an infinite update loop + * that checks whether all the WebGL context are still valid (not + * lost). If some WebGL context is lost, it is automatically + * reinitialized by created a fresh HTML5 canvas. + **/ + class WebGLViewportsRegistry : public boost::noncopyable, + public boost::enable_shared_from_this + { + private: + typedef std::map > Viewports; + + double timeoutMS_; + Viewports viewports_; + long timeOutID_; + + void LaunchTimer(); + + void OnTimeout(); + + static void OnTimeoutCallback(void *userData); + + public: + explicit WebGLViewportsRegistry(double timeoutMS /* in milliseconds */); + + ~WebGLViewportsRegistry(); + + boost::shared_ptr Add(const std::string& canvasId); + + void Remove(const std::string& canvasId); + + void Clear(); + + class Accessor : public boost::noncopyable + { + private: + WebGLViewportsRegistry& that_; + std::unique_ptr lock_; + + public: + Accessor(WebGLViewportsRegistry& that, + const std::string& canvasId); + + bool IsValid() const + { + return lock_.get() != NULL; + } + + IViewport::ILock& GetViewport() const; + }; + + + + static void FinalizeGlobalRegistry(); + + static void SetGlobalRegistryTimeout(double timeout); + + static WebGLViewportsRegistry& GetGlobalRegistry(); + }; +}