# HG changeset patch # User Sebastien Jodogne # Date 1683123350 -7200 # Node ID 86e0e92a2e0dced8216a28793cac9e097961e9d0 # Parent f7cbc58ff44d6c7fe8d077c252b3f95a481ff568 added OpenGLTextureArray and OpenGLFramebuffer diff -r f7cbc58ff44d -r 86e0e92a2e0d OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake --- a/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake Mon Apr 24 17:10:41 2023 +0200 +++ b/OrthancStone/Resources/CMake/OrthancStoneConfiguration.cmake Wed May 03 16:15:50 2023 +0200 @@ -501,6 +501,10 @@ ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLShader.cpp ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLTexture.h ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLTexture.cpp + ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLTextureArray.h + ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLTextureArray.cpp + ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLFramebuffer.h + ${ORTHANC_STONE_ROOT}/OpenGL/OpenGLFramebuffer.cpp ${ORTHANC_STONE_ROOT}/Scene2D/OpenGLCompositor.h ${ORTHANC_STONE_ROOT}/Scene2D/OpenGLCompositor.cpp diff -r f7cbc58ff44d -r 86e0e92a2e0d OrthancStone/Sources/OpenGL/OpenGLFramebuffer.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/OpenGL/OpenGLFramebuffer.cpp Wed May 03 16:15:50 2023 +0200 @@ -0,0 +1,261 @@ +/** + * 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 "OpenGLFramebuffer.h" + +#if defined(__EMSCRIPTEN__) +# if !defined(ORTHANC_WEBGL2_HEAP_COMPAT) +# error The macro ORTHANC_WEBGL2_HEAP_COMPAT must be defined +# endif +#endif + +#include "OpenGLTexture.h" +#include "OpenGLTextureArray.h" + +#include + + +namespace OrthancStone +{ + namespace OpenGL + { + void OpenGLFramebuffer::SetupTextureTarget() + { + GLenum drawBuffers[1] = { GL_COLOR_ATTACHMENT0 }; + glDrawBuffers(1, drawBuffers); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Incomplete setup of an OpenGL framebuffer"); + } + } + + + void OpenGLFramebuffer::ReadContent(Orthanc::ImageAccessor& target) + { + if (target.GetPitch() != target.GetWidth() * Orthanc::GetBytesPerPixel(target.GetFormat()) || + target.GetBuffer() == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Image must have minimal pitch"); + } + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) + { + ORTHANC_OPENGL_CHECK("glCheckFramebufferStatus()"); + + glViewport(0, 0, target.GetWidth(), target.GetHeight()); + + GLenum sourceFormat, internalFormat, pixelType; + OpenGLTexture::ConvertToOpenGLFormats(sourceFormat, internalFormat, pixelType, target.GetFormat()); + +#if defined(__EMSCRIPTEN__) && (ORTHANC_WEBGL2_HEAP_COMPAT == 1) + // Check out "OpenGLTexture.cpp" for an explanation + + int framebufferFormat, framebufferType; + glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &framebufferFormat); + glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &framebufferType); + + switch (target.GetFormat()) + { + case Orthanc::PixelFormat_RGBA32: + if (sourceFormat != GL_RGBA || + internalFormat != GL_RGBA || + pixelType != GL_UNSIGNED_BYTE || + framebufferFormat != GL_RGBA || + framebufferType != GL_UNSIGNED_BYTE) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + EM_ASM({ + var ptr = emscriptenWebGLGetTexPixelData(GLctx.UNSIGNED_BYTE, GLctx.RGBA, $1, $2, $0, GLctx.RGBA); + GLctx.readPixels(0, 0, $1, $2, GLctx.RGBA, GLctx.UNSIGNED_BYTE, ptr); + }, + target.GetBuffer(), // $0 + target.GetWidth(), // $1 + target.GetHeight()); // $2 + break; + + case Orthanc::PixelFormat_Float32: + // In Mozilla Firefox, "Float32" is not available as such. We + // have to download an RGBA image in Float32. + if (sourceFormat != GL_RED || + internalFormat != GL_R32F || + pixelType != GL_FLOAT || + framebufferType != GL_FLOAT) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + switch (framebufferFormat) + { + case GL_RGBA: + // This is Mozilla Firefox + EM_ASM({ + var tmp = new Float32Array($1 * $2 * 4); + GLctx.readPixels(0, 0, $1, $2, GLctx.RGBA, GLctx.FLOAT, tmp); + + // From RGBA to RED + var ptr = emscriptenWebGLGetTexPixelData(GLctx.FLOAT, GLctx.RED, $1, $2, $0, GLctx.R32F); + for (var i = 0; i < $1 * $2; i++) { + ptr[i] = tmp[4 * i]; + } + }, + target.GetBuffer(), // $0 + target.GetWidth(), // $1 + target.GetHeight()); // $2 + break; + + case GL_RED: + // This is Chromium + EM_ASM({ + var ptr = emscriptenWebGLGetTexPixelData(GLctx.FLOAT, GLctx.RED, $1, $2, $0, GLctx.R32F); + GLctx.readPixels(0, 0, $1, $2, GLctx.RED, GLctx.FLOAT, ptr); + }, + target.GetBuffer(), // $0 + target.GetWidth(), // $1 + target.GetHeight()); // $2 + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } +#else + glReadPixels(0, 0, target.GetWidth(), target.GetHeight(), sourceFormat, pixelType, target.GetBuffer()); +#endif + + ORTHANC_OPENGL_CHECK("glReadPixels()"); + } + else + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Incomplete setup of an OpenGL framebuffer"); + } + } + + + OpenGLFramebuffer::OpenGLFramebuffer(IOpenGLContext& context) : + context_(context), + framebuffer_(0) + { + if (context.IsContextLost()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "OpenGL context has been lost"); + } + + glGenFramebuffers(1, &framebuffer_); + if (framebuffer_ == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Cannot create an OpenGL framebuffer"); + } + + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_); + } + + + OpenGLFramebuffer::~OpenGLFramebuffer() + { + glDeleteFramebuffers(1, &framebuffer_); + } + + + void OpenGLFramebuffer::SetTarget(OpenGLTexture& target) + { + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target.GetId(), 0); + SetupTextureTarget(); + glViewport(0, 0, target.GetWidth(), target.GetHeight()); + } + + + void OpenGLFramebuffer::SetTarget(OpenGLTextureArray& target, + unsigned int layer) + { + if (layer >= target.GetDepth()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, target.GetId(), 0, layer); + SetupTextureTarget(); + glViewport(0, 0, target.GetWidth(), target.GetHeight()); + } + } + + + void OpenGLFramebuffer::ReadTexture(Orthanc::ImageAccessor& target, + const OpenGLTexture& source) + { + if (target.GetWidth() != source.GetWidth() || + target.GetHeight() != source.GetHeight()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + else if (target.GetFormat() != source.GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + else + { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, source.GetId(), 0); + ORTHANC_OPENGL_CHECK("glFramebufferTexture2D()"); + ReadContent(target); + } + } + + + void OpenGLFramebuffer::ReadTexture(Orthanc::ImageAccessor& target, + const OpenGLTextureArray& source, + unsigned int layer) + { + if (target.GetWidth() != source.GetWidth() || + target.GetHeight() != source.GetHeight()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + else if (target.GetFormat() != source.GetFormat()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); + } + else if (layer >= source.GetDepth()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, source.GetId(), 0, layer); + ORTHANC_OPENGL_CHECK("glFramebufferTextureLayer()"); + ReadContent(target); + } + } + } +} diff -r f7cbc58ff44d -r 86e0e92a2e0d OrthancStone/Sources/OpenGL/OpenGLFramebuffer.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/OpenGL/OpenGLFramebuffer.h Wed May 03 16:15:50 2023 +0200 @@ -0,0 +1,66 @@ +/** + * 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 "OpenGLIncludes.h" +#include "IOpenGLContext.h" + +#include + +namespace OrthancStone +{ + namespace OpenGL + { + class OpenGLTexture; + class OpenGLTextureArray; + + class OpenGLFramebuffer : public boost::noncopyable + { + private: + IOpenGLContext& context_; + GLuint framebuffer_; + + void SetupTextureTarget(); + + void ReadContent(Orthanc::ImageAccessor& target); + + public: + OpenGLFramebuffer(IOpenGLContext& context); + + ~OpenGLFramebuffer(); + + void SetTarget(OpenGLTexture& target); + + void SetTarget(OpenGLTextureArray& target, + unsigned int layer); + + void ReadTexture(Orthanc::ImageAccessor& target, + const OpenGLTexture& source); + + void ReadTexture(Orthanc::ImageAccessor& target, + const OpenGLTextureArray& source, + unsigned int layer); + }; + } +} diff -r f7cbc58ff44d -r 86e0e92a2e0d OrthancStone/Sources/OpenGL/OpenGLTexture.cpp --- a/OrthancStone/Sources/OpenGL/OpenGLTexture.cpp Mon Apr 24 17:10:41 2023 +0200 +++ b/OrthancStone/Sources/OpenGL/OpenGLTexture.cpp Wed May 03 16:15:50 2023 +0200 @@ -22,11 +22,6 @@ #include "OpenGLTexture.h" -#include "IOpenGLContext.h" - -#include -#include -#include #if defined(__EMSCRIPTEN__) # if !defined(ORTHANC_WEBGL2_HEAP_COMPAT) @@ -34,16 +29,20 @@ # endif #endif +#include +#include +#include + namespace OrthancStone { namespace OpenGL { OpenGLTexture::OpenGLTexture(OpenGL::IOpenGLContext& context) : + context_(context), texture_(0), width_(0), height_(0), format_(Orthanc::PixelFormat_Grayscale8), - context_(context), isLinearInterpolation_(false) { if (!context_.IsContextLost()) @@ -275,25 +274,26 @@ void OpenGLTexture::SetClampingToZero() { - ORTHANC_OPENGL_CHECK("Entering OpenGLTexture::SetClampingToZero()"); - #if defined(__EMSCRIPTEN__) /** * This is because WebGL 2 derives from OpenGL ES 3.0, which * doesn't support GL_CLAMP_TO_BORDER, as can be seen here: * https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexParameter.xhtml **/ - LOG(WARNING) << "OpenGLTexture::SetClampingToZero() is not available in WebGL 2"; + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "OpenGLTextureArray::SetClampingToZero() is not available in WebGL 2"); #else + ORTHANC_OPENGL_CHECK("Entering OpenGLTexture::SetClampingToZero()"); + glBindTexture(GL_TEXTURE_2D, texture_); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); GLfloat colorfv[4] = { 0, 0, 0, 0 }; glTextureParameterfv(texture_, GL_TEXTURE_BORDER_COLOR, colorfv); + + ORTHANC_OPENGL_CHECK("Exiting OpenGLTexture::SetClampingToZero()"); #endif - - ORTHANC_OPENGL_CHECK("Exiting OpenGLTexture::SetClampingToZero()"); } diff -r f7cbc58ff44d -r 86e0e92a2e0d OrthancStone/Sources/OpenGL/OpenGLTexture.h --- a/OrthancStone/Sources/OpenGL/OpenGLTexture.h Mon Apr 24 17:10:41 2023 +0200 +++ b/OrthancStone/Sources/OpenGL/OpenGLTexture.h Wed May 03 16:15:50 2023 +0200 @@ -37,13 +37,14 @@ { class OpenGLTexture : public boost::noncopyable { + friend class OpenGLFramebuffer; + private: - GLuint texture_; - unsigned int width_; - unsigned int height_; - + OpenGL::IOpenGLContext& context_; + GLuint texture_; + unsigned int width_; + unsigned int height_; Orthanc::PixelFormat format_; - OpenGL::IOpenGLContext& context_; bool isLinearInterpolation_; void Setup(Orthanc::PixelFormat format, @@ -52,11 +53,6 @@ bool isLinearInterpolation, const void* data); - public: - explicit OpenGLTexture(OpenGL::IOpenGLContext& context); - - ~OpenGLTexture(); - /** * Returns the low-level OpenGL handle of the texture. Beware to * never change the size of the texture using this handle! @@ -66,6 +62,11 @@ return texture_; } + public: + explicit OpenGLTexture(OpenGL::IOpenGLContext& context); + + ~OpenGLTexture(); + Orthanc::PixelFormat GetFormat() const { return format_; diff -r f7cbc58ff44d -r 86e0e92a2e0d OrthancStone/Sources/OpenGL/OpenGLTextureArray.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/OpenGL/OpenGLTextureArray.cpp Wed May 03 16:15:50 2023 +0200 @@ -0,0 +1,308 @@ +/** + * 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 "OpenGLTextureArray.h" + +#if defined(__EMSCRIPTEN__) +# if !defined(ORTHANC_WEBGL2_HEAP_COMPAT) +# error The macro ORTHANC_WEBGL2_HEAP_COMPAT must be defined +# endif +#endif + +#include "OpenGLFramebuffer.h" +#include "OpenGLTexture.h" + +#include +#include + +#include +#include + +namespace OrthancStone +{ + namespace OpenGL + { + OpenGLTextureArray::OpenGLTextureArray(IOpenGLContext& context) : + context_(context), + texture_(0), + width_(0), + height_(0), + depth_(0), + format_(Orthanc::PixelFormat_Float32), + isLinearInterpolation_(false) + { + if (context.IsContextLost()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "OpenGL context has been lost"); + } + + glGenTextures(1, &texture_); + ORTHANC_OPENGL_CHECK("glGenTextures()"); + + if (texture_ == 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Cannot create an OpenGL texture array"); + } + } + + + OpenGLTextureArray::~OpenGLTextureArray() + { + assert(texture_ != 0); + glDeleteTextures(1, &texture_); + } + + + void OpenGLTextureArray::Setup(Orthanc::PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int depth, + bool isLinearInterpolation) + { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_); + ORTHANC_OPENGL_CHECK("glBindTexture(GL_TEXTURE_2D_ARRAY)"); + + GLenum sourceFormat, internalFormat, pixelType; + OpenGLTexture::ConvertToOpenGLFormats(sourceFormat, internalFormat, pixelType, format); + + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, internalFormat, width, height, depth, + 0, sourceFormat, pixelType, NULL); + ORTHANC_OPENGL_CHECK("glTexImage3D()"); + +#if !defined(__EMSCRIPTEN__) + /** + * glGetTexLevelParameteriv() was introduced in OpenGL ES 3.1, + * but WebGL 2 only supports OpenGL ES 3.0, so it is not + * available in WebAssembly: + * https://registry.khronos.org/OpenGL-Refpages/es3.1/html/glGetTexLevelParameter.xhtml + **/ + GLint w, h, d; + glGetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, 0, GL_TEXTURE_WIDTH, &w); + glGetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, 0, GL_TEXTURE_HEIGHT, &h); + glGetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, 0, GL_TEXTURE_DEPTH, &d); + if (width != static_cast(w) || + height != static_cast(h) || + depth != static_cast(d)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "Your GPU cannot create an array of textures of size " + + boost::lexical_cast(width) + " x " + + boost::lexical_cast(height) + " x " + + boost::lexical_cast(depth)); + } +#endif + + format_ = format; + width_ = width; + height_ = height; + depth_ = depth; + isLinearInterpolation_ = isLinearInterpolation; + + GLint interpolation = (isLinearInterpolation ? GL_LINEAR : GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, interpolation); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, interpolation); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + + void OpenGLTextureArray::SetClampingToZero() + { +#if defined(__EMSCRIPTEN__) + /** + * This is because WebGL 2 derives from OpenGL ES 3.0, which + * doesn't support GL_CLAMP_TO_BORDER, as can be seen here: + * https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexParameter.xhtml + **/ + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError, + "OpenGLTextureArray::SetClampingToZero() is not available in WebGL 2"); +#else + ORTHANC_OPENGL_CHECK("Entering OpenGLTextureArray::SetClampingToZero()"); + + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + + GLfloat colorfv[4] = { 0, 0, 0, 0 }; + glTexParameterfv(texture_, GL_TEXTURE_BORDER_COLOR, colorfv); + + ORTHANC_OPENGL_CHECK("Exiting OpenGLTextureArray::SetClampingToZero()"); +#endif + } + + + void OpenGLTextureArray::Bind(GLint location) const + { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_); + glUniform1i(location, 0 /* texture unit */); + } + + + void OpenGLTextureArray::BindAsTextureUnit(GLint location, + unsigned int unit) const + { + if (unit >= 32) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + assert(GL_TEXTURE0 + 1 == GL_TEXTURE1 && + GL_TEXTURE0 + 31 == GL_TEXTURE31); + + glActiveTexture(GL_TEXTURE0 + unit); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_); + glUniform1i(location, unit /* texture unit */); + } + + + void OpenGLTextureArray::Upload(const Orthanc::ImageAccessor& image, + unsigned int layer) + { + if (image.GetWidth() != width_ || + image.GetHeight() != height_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); + } + + if (layer >= depth_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (width_ != 0 && + height_ != 0) + { + GLenum sourceFormat, internalFormat, pixelType; + OpenGLTexture::ConvertToOpenGLFormats(sourceFormat, internalFormat, pixelType, image.GetFormat()); + + glBindTexture(GL_TEXTURE_2D_ARRAY, texture_); + +#if defined(__EMSCRIPTEN__) && (ORTHANC_WEBGL2_HEAP_COMPAT == 1) + // Check out "OpenGLTexture.cpp" for an explanation + EM_ASM({ + var ptr = emscriptenWebGLGetTexPixelData($5, $4, $2, $3, $0, $1); + GLctx.texSubImage3D(GLctx.TEXTURE_2D_ARRAY, 0, 0 /* x offset */, 0 /* y offset */, + $6, $2, $3, 1 /* depth */, $4, $5, ptr); + }, + image.GetConstBuffer(), // $0 + internalFormat, // $1 + image.GetWidth(), // $2 + image.GetHeight(), // $3 + sourceFormat, // $4 + pixelType, // $5 + layer); // $6 +#else + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0 /* x offset */, 0 /* y offset */, layer /* z offset */, + width_, height_, 1 /* depth */, sourceFormat, pixelType, image.GetConstBuffer()); +#endif + } + } + + + OpenGLTextureArray::DownloadedVolume::DownloadedVolume(const OpenGLTextureArray& texture) : + format_(texture.format_), + width_(texture.width_), + height_(texture.height_), + depth_(texture.depth_) + { + if (width_ != 0 && + height_ != 0 && + depth_ != 0) + { + buffer_.resize(Orthanc::GetBytesPerPixel(format_) * width_ * height_ * depth_); + assert(buffer_.size() > 0); + +#if 1 || defined(__EMSCRIPTEN__) + /** + * The "glGetTexImage()" function is unavailable in WebGL, it + * is necessary to use a framebuffer: + * https://stackoverflow.com/a/15064957 + **/ + OpenGLFramebuffer framebuffer(texture.context_); + + Orthanc::Image tmp(texture.GetFormat(), texture.GetWidth(), texture.GetHeight(), true); + + for (unsigned int layer = 0; layer < depth_; layer++) + { + framebuffer.ReadTexture(tmp, texture, layer); + memcpy(&buffer_[0] + layer * tmp.GetPitch() * height_, + tmp.GetBuffer(), tmp.GetPitch() * height_); + } + +#else + glBindTexture(GL_TEXTURE_2D_ARRAY, texture.texture_); + + switch (format) + { + case Orthanc::PixelFormat_Grayscale8: + glGetTexImage(GL_TEXTURE_2D_ARRAY, 0 /* base level */, GL_RED, GL_UNSIGNED_BYTE, &buffer_[0]); + break; + + case Orthanc::PixelFormat_RGB24: + glGetTexImage(GL_TEXTURE_2D_ARRAY, 0 /* base level */, GL_RGB, GL_UNSIGNED_BYTE, &buffer_[0]); + break; + + case Orthanc::PixelFormat_RGBA32: + glGetTexImage(GL_TEXTURE_2D_ARRAY, 0 /* base level */, GL_RGBA, GL_UNSIGNED_BYTE, &buffer_[0]); + break; + + case Orthanc::PixelFormat_Float32: + glGetTexImage(GL_TEXTURE_2D_ARRAY, 0 /* base level */, GL_RED, GL_FLOAT, &buffer_[0]); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } +#endif + } + } + + + Orthanc::ImageAccessor* OpenGLTextureArray::DownloadedVolume::GetLayer(unsigned int layer) const + { + if (layer >= depth_) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else if (width_ == 0 || + height_ == 0 || + depth_ == 0) + { + return new Orthanc::Image(format_, 0, 0, true); + } + else + { + const size_t rowSize = width_ * Orthanc::GetBytesPerPixel(format_); + + std::unique_ptr target(new Orthanc::ImageAccessor); + target->AssignReadOnly(format_, width_, height_, rowSize, + reinterpret_cast(buffer_.c_str()) + layer * height_ * rowSize); + return target.release(); + } + } + } +} diff -r f7cbc58ff44d -r 86e0e92a2e0d OrthancStone/Sources/OpenGL/OpenGLTextureArray.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancStone/Sources/OpenGL/OpenGLTextureArray.h Wed May 03 16:15:50 2023 +0200 @@ -0,0 +1,144 @@ +/** + * 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 "OpenGLIncludes.h" +#include "IOpenGLContext.h" + +#include + +namespace OrthancStone +{ + namespace OpenGL + { + class OpenGLTextureArray : public boost::noncopyable + { + friend class OpenGLFramebuffer; + + private: + OpenGL::IOpenGLContext& context_; + GLuint texture_; + unsigned int width_; + unsigned int height_; + unsigned int depth_; + Orthanc::PixelFormat format_; + bool isLinearInterpolation_; + + /** + * Returns the low-level OpenGL handle of the texture + * array. Beware to never change the size of the texture using + * this handle! + **/ + GLuint GetId() const + { + return texture_; + } + + public: + OpenGLTextureArray(IOpenGLContext& context); + + ~OpenGLTextureArray(); + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetDepth() const + { + return depth_; + } + + Orthanc::PixelFormat GetFormat() const + { + return format_; + } + + bool IsLinearInterpolation() const + { + return isLinearInterpolation_; + } + + void Setup(Orthanc::PixelFormat format, + unsigned int width, + unsigned int height, + unsigned int depth, + bool isLinearInterpolation); + + /** + * By default, textures are mirrored at the borders. This + * function will set out-of-image access to zero. + **/ + void SetClampingToZero(); + + void Bind(GLint location) const; + + void BindAsTextureUnit(GLint location, + unsigned int unit) const; + + void Upload(const Orthanc::ImageAccessor& image, + unsigned int layer); + + class DownloadedVolume : public boost::noncopyable + { + private: + std::string buffer_; + Orthanc::PixelFormat format_; + unsigned int width_; + unsigned int height_; + unsigned int depth_; + + public: + DownloadedVolume(const OpenGLTextureArray& texture); + + Orthanc::PixelFormat GetFormat() const + { + return format_; + } + + unsigned int GetWidth() const + { + return width_; + } + + unsigned int GetHeight() const + { + return height_; + } + + unsigned int GetDepth() const + { + return depth_; + } + + Orthanc::ImageAccessor* GetLayer(unsigned int layer) const; + }; + }; + } +} diff -r f7cbc58ff44d -r 86e0e92a2e0d OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.h --- a/OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.h Mon Apr 24 17:10:41 2023 +0200 +++ b/OrthancStone/Sources/Scene2D/AnnotationsSceneLayer.h Wed May 03 16:15:50 2023 +0200 @@ -21,6 +21,8 @@ **/ +#pragma once + #include "../Messages/IObservable.h" #include "Scene2D.h" #include "../Scene2DViewport/IFlexiblePointerTracker.h"