diff --git a/modules/core/include/opencv2/core/opengl.hpp b/modules/core/include/opencv2/core/opengl.hpp index 5d19f81f85..c8bea002ff 100644 --- a/modules/core/include/opencv2/core/opengl.hpp +++ b/modules/core/include/opencv2/core/opengl.hpp @@ -472,6 +472,244 @@ private: Buffer texCoord_; }; +/** @brief Struct used to describe a vertex array attribute. + */ +struct CV_EXPORTS Attribute +{ + /** @brief The target defines how you intend to use the buffer object. + */ + enum Type + { + BYTE = 0x1400, //!< 8-bit integer. + UNSIGNED_BYTE = 0x1401, //!< 8-bit unsigned integer. + SHORT = 0x1402, //!< 16-bit integer. + UNSIGNED_SHORT = 0x1403, //!< 16-bit unsigned integer. + INT = 0x1404, //!< 32-bit integer. + UNSIGNED_INT = 0x1405, //!< 32-bit unsigned integer. + FLOAT = 0x1406, //!< 32-bit floating point. + }; + + //! The buffer this attribute takes data from. + Buffer buffer_; + //! Number of bytes between each element described by this attribute. + size_t stride_; + //! Position of the first element in the buffer in bytes. + size_t offset_; + //! Number of components of the type (eg.: float = 1 component, vec3 = 3 components). + int size_; + //! Type of the vertex attribute. See cv::ogl::Attribute::Type. + Type type_; + //! Is the vertex attribute an integer? + bool integer_; + //! If the vertex attribute isn't an integer, should it be normalized? + bool normalized_; + //! Location of the attribute in the program. See cv::ogl::Program::getAttributeLocation. + unsigned int shader_loc_; +}; + +/** @brief Smart pointer for OpenGL vertex arrays with reference counting. + */ +class CV_EXPORTS VertexArray +{ +public: + /** @brief The constructors. + + Creates empty ogl::VertexArray object, creates ogl::VertexArray object from a list of attributes. + */ + VertexArray(); + + /** @overload + @param attributes List of attributes. + @param autoRelease Auto release mode (if true, release will be called in object's destructor). + */ + VertexArray(std::initializer_list attributes, bool autoRelease = false); + + /** @brief Creates ogl::VertexArray object from a list of attributes. + + @param attributes List of attributes. + @param autoRelease Auto release mode (if true, release will be called in object's destructor). + */ + void create(std::initializer_list attributes, bool autoRelease = false); + + /** @brief Decrements the reference counter and destroys the vertex array object if needed. + + The function will call setAutoRelease(true) . + */ + void release(); + + /** @brief Sets auto release mode. + + The lifetime of the OpenGL object is tied to the lifetime of the context. If OpenGL context was + bound to a window it could be released at any time (user can close a window). If object's destructor + is called after destruction of the context it will cause an error. Thus ogl::VertexArray doesn't destroy + OpenGL object in destructor by default (all OpenGL resources will be released with OpenGL context). + This function can force ogl::VertexArray destructor to destroy OpenGL object. + @param flag Auto release mode (if true, release will be called in object's destructor). + */ + void setAutoRelease(bool flag); + + /** @brief Binds OpenGL vertex array. + */ + void bind() const; + + /** @brief Unbind any vertex array. + */ + static void unbind(); + + //! get OpenGL object id + unsigned int vaId() const; + + class Impl; + +private: + Ptr impl_; +}; + +/** @brief Smart pointer for OpenGL shader with reference counting. + */ +class CV_EXPORTS Shader +{ +public: + /** @brief The target defines how you intend to use the buffer object. + */ + enum ShaderType + { + FRAGMENT = 0x8B30, //!< The shader will be a fragment shader + VERTEX = 0x8B31, //!< The shader will be a vertex shader + }; + + /** @brief The constructors. + + Creates empty ogl::Shader object, creates ogl::Shader object from source code. + */ + Shader(); + + /** @overload + @param src Shader source code. + @param type Shader type. See cv::ogl::Shader::ShaderType. + @param autoRelease Auto release mode (if true, release will be called in object's destructor). + */ + Shader(const char* src, ShaderType type, bool autoRelease = false); + + /** @brief Creates ogl::Shader object from source code. + + @param src Shader source code. + @param type Shader type. See cv::ogl::Shader::ShaderType. + @param autoRelease Auto release mode (if true, release will be called in object's destructor). + */ + void create(const char* src, ShaderType type, bool autoRelease = false); + + /** @brief Decrements the reference counter and destroys the shader object if needed. + + The function will call setAutoRelease(true) . + */ + void release(); + + /** @brief Sets auto release mode. + + The lifetime of the OpenGL object is tied to the lifetime of the context. If OpenGL context was + bound to a window it could be released at any time (user can close a window). If object's destructor + is called after destruction of the context it will cause an error. Thus ogl::Shader doesn't destroy + OpenGL object in destructor by default (all OpenGL resources will be released with OpenGL context). + This function can force ogl::Shader destructor to destroy OpenGL object. + @param flag Auto release mode (if true, release will be called in object's destructor). + */ + void setAutoRelease(bool flag); + + ShaderType type() const; + + //! get OpenGL object id + unsigned int shaderId() const; + + class Impl; + +private: + Ptr impl_; + ShaderType type_; +}; + +/** @brief Smart pointer for OpenGL program with reference counting. + */ +class CV_EXPORTS Program +{ +public: + /** @brief The constructors. + + Creates empty ogl::Program object, creates ogl::Program object from a group of shader objects. + */ + Program(); + + /** @overload + @param vert Vertex shader. See cv::ogl::Shader. + @param frag Fragment shader. See cv::ogl::Shader. + @param autoRelease Auto release mode (if true, release will be called in object's destructor). + */ + Program(const Shader& vert, const Shader& frag, bool autoRelease = false); + + /** @brief Creates ogl::Program object from a group of shader objects. + + @param vert Vertex shader. See cv::ogl::Shader. + @param frag Fragment shader. See cv::ogl::Shader. + @param autoRelease Auto release mode (if true, release will be called in object's destructor). + */ + void create(const Shader& vert, const Shader& frag, bool autoRelease = false); + + /** @brief Decrements the reference counter and destroys the program object if needed. + + The function will call setAutoRelease(true) . + */ + void release(); + + /** @brief Sets auto release mode. + + The lifetime of the OpenGL object is tied to the lifetime of the context. If OpenGL context was + bound to a window it could be released at any time (user can close a window). If object's destructor + is called after destruction of the context it will cause an error. Thus ogl::Program doesn't destroy + OpenGL object in destructor by default (all OpenGL resources will be released with OpenGL context). + This function can force ogl::Program destructor to destroy OpenGL object. + @param flag Auto release mode (if true, release will be called in object's destructor). + */ + void setAutoRelease(bool flag); + + /** @brief Binds OpenGL shader program. + */ + void bind() const; + + /** @brief Unbind any shader program. + */ + static void unbind(); + + /** @brief Returns the shader location of an attribute from its name. + @param name Attribute name. + */ + int getAttributeLocation(const char* name) const; + + /** @brief Returns the shader location of an uniform from its name. + @param name Uniform name. + */ + int getUniformLocation(const char* name) const; + + /** @brief Sets a uniform vector value. + @param location Uniform location. + @param vec Vector value. + */ + static void setUniformVec3(int location, Vec3f vec); + + /** @brief Sets a uniform matrix value. + @param location Uniform location. + @param mat Matrix value. + */ + static void setUniformMat4x4(int location, Matx44f mat); + + //! get OpenGL object id + unsigned int programId() const; + + class Impl; + +private: + Ptr impl_; +}; + /////////////////// Render Functions /////////////////// //! render mode @@ -488,6 +726,18 @@ enum RenderModes { POLYGON = 0x0009 }; +//! index type +enum IndexType { + UNSIGNED_BYTE = 0x1401, + UNSIGNED_SHORT = 0x1403, + UNSIGNED_INT = 0x1405, +}; + +//! capability +enum Capability { + DEPTH_TEST = 0x0B71, +}; + /** @brief Render OpenGL texture or primitives. @param tex Texture to draw. @param wndRect Region of window, where to draw a texture (normalized coordinates). @@ -512,6 +762,41 @@ CV_EXPORTS void render(const Arrays& arr, int mode = POINTS, Scalar color = Scal */ CV_EXPORTS void render(const Arrays& arr, InputArray indices, int mode = POINTS, Scalar color = Scalar::all(255)); +/** @brief Clears the current target to a single color. +@param color New color. +*/ +CV_EXPORTS void clearColor(Scalar color = Scalar::all(0)); + +/** @brief Clears the current target depth to a single value. +@param depth New depth. +*/ +CV_EXPORTS void clearDepth(float depth); + +/** @brief Renders primitives from array data. +@param first Index of the first vertex to draw. +@param count Number of vertices to draw. +@param mode Rendering mode. See cv::ogl::RenderModes. +*/ +CV_EXPORTS void drawArrays(int first, int count, int mode); + +/** @brief Renders primitives from array data with index buffer set. +@param first Byte offset of the first index in the bound element buffer. +@param count Number of indices to draw. +@param type Index type. See cv::ogl::IndexType. +@param mode Rendering mode. See cv::ogl::RenderModes. +*/ +CV_EXPORTS void drawElements(int first, int count, int type, int mode); + +/** @brief Enable server-side GL capabilities. +@param cap The capability to enable. +*/ +CV_EXPORTS void enable(int cap); + +/** @brief Disable server-side GL capabilities. +@param cap The capability to disable. +*/ +CV_EXPORTS void disable(int cap); + /////////////////// CL-GL Interoperability Functions /////////////////// namespace ocl { diff --git a/modules/core/src/opengl.cpp b/modules/core/src/opengl.cpp index b7a85a61a4..524591a27f 100644 --- a/modules/core/src/opengl.cpp +++ b/modules/core/src/opengl.cpp @@ -316,6 +316,8 @@ public: void copyFrom(GLsizeiptr size, const GLvoid* data); void copyTo(GLsizeiptr size, GLvoid* data) const; + void resize(GLsizeiptr size, GLenum target); + void* mapHost(GLenum access); void unmapHost(); @@ -407,6 +409,19 @@ void cv::ogl::Buffer::Impl::copyFrom(GLsizeiptr size, const GLvoid* data) CV_CheckGlError(); } +void cv::ogl::Buffer::Impl::resize(GLsizeiptr size, GLenum target) +{ + gl::BindBuffer(target, bufId_); + CV_CheckGlError(); + + // keep the same buffer id so bound vertex arrays stay valid + gl::BufferData(target, size, 0, gl::DYNAMIC_DRAW); + CV_CheckGlError(); + + gl::BindBuffer(target, 0); + CV_CheckGlError(); +} + void cv::ogl::Buffer::Impl::copyTo(GLsizeiptr size, GLvoid* data) const { gl::BindBuffer(gl::COPY_READ_BUFFER, bufId_); @@ -547,8 +562,15 @@ void cv::ogl::Buffer::create(int arows, int acols, int atype, Target target, boo #else if (rows_ != arows || cols_ != acols || type_ != atype) { - const GLsizeiptr asize = arows * acols * CV_ELEM_SIZE(atype); - impl_.reset(new Impl(asize, 0, target, autoRelease)); + const GLsizeiptr asize = (GLsizeiptr)arows * acols * CV_ELEM_SIZE(atype); + // resize in place when sole owner (keeps the GL id); else allocate fresh (copy-on-write) + if (impl_->bufId() != 0 && impl_.use_count() == 1) + { + impl_->resize(asize, target); + impl_->setAutoRelease(autoRelease); + } + else + impl_.reset(new Impl(asize, 0, target, autoRelease)); rows_ = arows; cols_ = acols; type_ = atype; @@ -1411,6 +1433,551 @@ void cv::ogl::Arrays::bind() const #endif } +//////////////////////////////////////////////////////////////////////// +// ogl::VertexArray + +#ifndef HAVE_OPENGL + +class cv::ogl::VertexArray::Impl +{ +}; + +#else + +class cv::ogl::VertexArray::Impl +{ +public: + static const Ptr& empty(); + + Impl(GLuint vaId, bool autoRelease); + Impl(std::initializer_list attributes, bool autoRelease); + ~Impl(); + + void bind(); + + void setAutoRelease(bool flag) { autoRelease_ = flag; } + + GLuint vaId() const { return vaId_; } + +private: + Impl(); + + GLuint vaId_; + bool autoRelease_; +}; + +const Ptr& cv::ogl::VertexArray::Impl::empty() +{ + static Ptr p(new Impl); + return p; +} + +cv::ogl::VertexArray::Impl::Impl() : vaId_(0), autoRelease_(false) +{ +} + +cv::ogl::VertexArray::Impl::Impl(GLuint vaId, bool autoRelease) : vaId_(vaId), autoRelease_(autoRelease) +{ + CV_Assert( gl::IsVertexArray(vaId) == gl::TRUE_ ); +} + +cv::ogl::VertexArray::Impl::Impl(std::initializer_list attributes, bool autoRelease) : vaId_(0), autoRelease_(autoRelease) +{ + gl::GenVertexArrays(1, &vaId_); + CV_CheckGlError(); + + CV_Assert( vaId_ != 0 ); + gl::BindVertexArray(vaId_); + CV_CheckGlError(); + + for (auto& attribute : attributes) + { + attribute.buffer_.bind(Buffer::ARRAY_BUFFER); + if (attribute.integer_) + { + gl::VertexAttribIPointer( + attribute.shader_loc_, + attribute.size_, + attribute.type_, + static_cast(attribute.stride_), + reinterpret_cast(attribute.offset_) + ); + CV_CheckGlError(); + } + else + { + gl::VertexAttribPointer( + attribute.shader_loc_, + attribute.size_, + attribute.type_, + attribute.normalized_, + static_cast(attribute.stride_), + reinterpret_cast(attribute.offset_) + ); + CV_CheckGlError(); + } + + gl::EnableVertexAttribArray(attribute.shader_loc_); + CV_CheckGlError(); + } +} + +cv::ogl::VertexArray::Impl::~Impl() +{ + if (autoRelease_ && vaId_) + gl::DeleteVertexArrays(1, &vaId_); +} + +void cv::ogl::VertexArray::Impl::bind() +{ + gl::BindVertexArray(vaId_); + CV_CheckGlError(); +} + +#endif // HAVE_OPENGL + +cv::ogl::VertexArray::VertexArray() +{ +#ifndef HAVE_OPENGL + throw_no_ogl(); +#else + impl_ = Impl::empty(); +#endif +} + +cv::ogl::VertexArray::VertexArray(std::initializer_list attributes, bool autoRelease) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(attributes); CV_UNUSED(autoRelease); + throw_no_ogl(); +#else + impl_.reset(new Impl(attributes, autoRelease)); +#endif +} + +void cv::ogl::VertexArray::create(std::initializer_list attributes, bool autoRelease) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(attributes); CV_UNUSED(autoRelease); + throw_no_ogl(); +#else + impl_.reset(new Impl(attributes, autoRelease)); +#endif +} + +void cv::ogl::VertexArray::release() +{ +#ifdef HAVE_OPENGL + if (impl_) + impl_->setAutoRelease(true); + impl_ = Impl::empty(); +#endif +} + +void cv::ogl::VertexArray::setAutoRelease(bool flag) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(flag); + throw_no_ogl(); +#else + impl_->setAutoRelease(flag); +#endif +} + +void cv::ogl::VertexArray::bind() const +{ +#ifndef HAVE_OPENGL + throw_no_ogl(); +#else + impl_->bind(); +#endif +} + +void cv::ogl::VertexArray::unbind() +{ +#ifndef HAVE_OPENGL + throw_no_ogl(); +#else + gl::BindVertexArray(0); +#endif +} + +unsigned int cv::ogl::VertexArray::vaId() const +{ +#ifndef HAVE_OPENGL + throw_no_ogl(); +#else + return impl_->vaId(); +#endif +} + +//////////////////////////////////////////////////////////////////////// +// ogl::Shader + +#ifndef HAVE_OPENGL + +class cv::ogl::Shader::Impl +{ +}; + +#else + +class cv::ogl::Shader::Impl +{ +public: + static const Ptr& empty(); + + Impl(GLuint shaderId, bool autoRelease); + Impl(const char* src, GLenum type, bool autoRelease); + ~Impl(); + + void setAutoRelease(bool flag) { autoRelease_ = flag; } + + GLuint shaderId() const { return shaderId_; } + +private: + Impl(); + + GLuint shaderId_; + bool autoRelease_; +}; + +const Ptr& cv::ogl::Shader::Impl::empty() +{ + static Ptr p(new Impl); + return p; +} + +cv::ogl::Shader::Impl::Impl() : shaderId_(0), autoRelease_(false) +{ +} + +cv::ogl::Shader::Impl::Impl(GLuint shaderId, bool autoRelease) : shaderId_(shaderId), autoRelease_(autoRelease) +{ + CV_Assert( gl::IsShader(shaderId) == gl::TRUE_ ); +} + +cv::ogl::Shader::Impl::Impl(const char* src, GLenum type, bool autoRelease) : shaderId_(0), autoRelease_(autoRelease) +{ + shaderId_ = gl::CreateShader(type); + CV_CheckGlError(); + + CV_Assert( shaderId_ != 0 ); + + GLint status; + gl::ShaderSource(shaderId_, 1, &src, 0); + gl::CompileShader(shaderId_); + gl::GetShaderiv(shaderId_, gl::COMPILE_STATUS, &status); + if (!status) { + String info_log; + gl::GetShaderiv(shaderId_, gl::INFO_LOG_LENGTH, &status); + info_log.resize(status); + gl::GetShaderInfoLog(shaderId_, GLsizei(info_log.size()) + 1, nullptr, &info_log[0]); + CV_Error(Error::OpenGlApiCallError, info_log); + } + + CV_CheckGlError(); +} + +cv::ogl::Shader::Impl::~Impl() +{ + if (autoRelease_ && shaderId_) + gl::DeleteShader(shaderId_); +} + +#endif // HAVE_OPENGL + +cv::ogl::Shader::Shader() : type_(ShaderType::VERTEX) +{ +#ifndef HAVE_OPENGL + throw_no_ogl(); +#else + impl_ = Impl::empty(); +#endif +} + +cv::ogl::Shader::Shader(const char* src, ShaderType type, bool autoRelease) : type_(type) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(src); CV_UNUSED(type); CV_UNUSED(autoRelease); + throw_no_ogl(); +#else + impl_.reset(new Impl(src, type, autoRelease)); +#endif +} + +void cv::ogl::Shader::create(const char* src, ShaderType type, bool autoRelease) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(src); CV_UNUSED(type); CV_UNUSED(autoRelease); + throw_no_ogl(); +#else + impl_.reset(new Impl(src, type, autoRelease)); + type_ = type; +#endif +} + +void cv::ogl::Shader::release() +{ +#ifdef HAVE_OPENGL + if (impl_) + impl_->setAutoRelease(true); + impl_ = Impl::empty(); +#endif +} + +void cv::ogl::Shader::setAutoRelease(bool flag) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(flag); + throw_no_ogl(); +#else + impl_->setAutoRelease(flag); +#endif +} + +unsigned int cv::ogl::Shader::shaderId() const +{ +#ifndef HAVE_OPENGL + throw_no_ogl(); +#else + return impl_->shaderId(); +#endif +} + +cv::ogl::Shader::ShaderType cv::ogl::Shader::type() const +{ + return type_; +} + +//////////////////////////////////////////////////////////////////////// +// ogl::Program + +#ifndef HAVE_OPENGL + +class cv::ogl::Program::Impl +{ +}; + +#else + +class cv::ogl::Program::Impl +{ +public: + static const Ptr& empty(); + + Impl(GLuint programId, bool autoRelease); + Impl(const Shader& vert, const Shader& frag, bool autoRelease); + ~Impl(); + + void bind(); + + int getAttributeLocation(const char* name) const; + int getUniformLocation(const char* name) const; + + void setAutoRelease(bool flag) { autoRelease_ = flag; } + + GLuint programId() const { return programId_; } + +private: + Impl(); + + Shader vert_; + Shader frag_; + GLuint programId_; + bool autoRelease_; +}; + +const Ptr& cv::ogl::Program::Impl::empty() +{ + static Ptr p(new Impl); + return p; +} + +cv::ogl::Program::Impl::Impl() : programId_(0), autoRelease_(false) +{ +} + +cv::ogl::Program::Impl::Impl(GLuint programId, bool autoRelease) : programId_(programId), autoRelease_(autoRelease) +{ + CV_Assert( gl::IsProgram(programId) == gl::TRUE_ ); +} + +cv::ogl::Program::Impl::Impl(const Shader& vert, const Shader& frag, bool autoRelease) : programId_(0), autoRelease_(autoRelease) +{ + programId_ = gl::CreateProgram(); + CV_CheckGlError(); + + CV_Assert( programId_ != 0 ); + + gl::AttachShader(programId_, vert.shaderId()); + CV_CheckGlError(); + + gl::AttachShader(programId_, frag.shaderId()); + CV_CheckGlError(); + + GLint status; + gl::LinkProgram(programId_); + gl::GetProgramiv(programId_, gl::LINK_STATUS, &status); + if (!status) { + String info_log; + gl::GetProgramiv(programId_, gl::INFO_LOG_LENGTH, &status); + info_log.resize(status); + gl::GetProgramInfoLog(programId_, GLsizei(info_log.size()) + 1, nullptr, &info_log[0]); + CV_Error(Error::OpenGlApiCallError, info_log); + } + + CV_CheckGlError(); + + vert_ = vert; + frag_ = frag; +} + +cv::ogl::Program::Impl::~Impl() +{ + if (autoRelease_ && programId_) + gl::DeleteProgram(programId_); +} + +void cv::ogl::Program::Impl::bind() +{ + gl::UseProgram(programId_); + CV_CheckGlError(); +} + +int cv::ogl::Program::Impl::getAttributeLocation(const char* name) const +{ + // -1 if absent (e.g. optimized out); caller decides. + int location = gl::GetAttribLocation(programId_, name); + CV_CheckGlError(); + return location; +} + +int cv::ogl::Program::Impl::getUniformLocation(const char* name) const +{ + // -1 if absent (e.g. optimized out); caller decides. + int location = gl::GetUniformLocation(programId_, name); + CV_CheckGlError(); + return location; +} + +#endif // HAVE_OPENGL + +cv::ogl::Program::Program() +{ +#ifndef HAVE_OPENGL + throw_no_ogl(); +#else + impl_ = Impl::empty(); +#endif +} + +cv::ogl::Program::Program(const Shader& vert, const Shader& frag, bool autoRelease) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(vert); CV_UNUSED(frag); CV_UNUSED(autoRelease); + throw_no_ogl(); +#else + impl_.reset(new Impl(vert, frag, autoRelease)); +#endif +} + +void cv::ogl::Program::create(const Shader& vert, const Shader& frag, bool autoRelease) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(vert); CV_UNUSED(frag); CV_UNUSED(autoRelease); + throw_no_ogl(); +#else + impl_.reset(new Impl(vert, frag, autoRelease)); +#endif +} + +void cv::ogl::Program::release() +{ +#ifdef HAVE_OPENGL + if (impl_) + impl_->setAutoRelease(true); + impl_ = Impl::empty(); +#endif +} + +void cv::ogl::Program::setAutoRelease(bool flag) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(flag); + throw_no_ogl(); +#else + impl_->setAutoRelease(flag); +#endif +} + +void cv::ogl::Program::bind() const +{ +#ifndef HAVE_OPENGL + throw_no_ogl(); +#else + impl_->bind(); +#endif +} + +void cv::ogl::Program::unbind() +{ +#ifndef HAVE_OPENGL + throw_no_ogl(); +#else + gl::UseProgram(0); +#endif +} + +int cv::ogl::Program::getAttributeLocation(const char* name) const +{ +#ifndef HAVE_OPENGL + CV_UNUSED(name); + throw_no_ogl(); +#else + return impl_->getAttributeLocation(name); +#endif +} + +int cv::ogl::Program::getUniformLocation(const char* name) const +{ +#ifndef HAVE_OPENGL + CV_UNUSED(name); + throw_no_ogl(); +#else + return impl_->getUniformLocation(name); +#endif +} + +void cv::ogl::Program::setUniformVec3(int loc, Vec3f vec) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(loc); CV_UNUSED(vec); + throw_no_ogl(); +#else + gl::Uniform3f(loc, vec(0), vec(1), vec(2)); +#endif +} + +void cv::ogl::Program::setUniformMat4x4(int loc, Matx44f mat) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(loc); CV_UNUSED(mat); + throw_no_ogl(); +#else + gl::UniformMatrix4fv(loc, 1, true, mat.val); +#endif +} + +unsigned int cv::ogl::Program::programId() const +{ +#ifndef HAVE_OPENGL + throw_no_ogl(); +#else + return impl_->programId(); +#endif +} + //////////////////////////////////////////////////////////////////////// // Rendering @@ -1577,6 +2144,69 @@ void cv::ogl::render(const ogl::Arrays& arr, InputArray indices, int mode, Scala #endif } +void cv::ogl::clearColor(Scalar color) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(color); + throw_no_ogl(); +#else + gl::ClearColor(static_cast(color[0] / 255.0), static_cast(color[1] / 255.0), + static_cast(color[2] / 255.0), static_cast(color[3] / 255.0)); + gl::Clear(gl::COLOR_BUFFER_BIT); +#endif // HAVE_OPENGL +} + +void cv::ogl::clearDepth(float depth) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(depth); + throw_no_ogl(); +#else + gl::ClearDepth(depth); + gl::Clear(gl::DEPTH_BUFFER_BIT); +#endif // HAVE_OPENGL +} + +void cv::ogl::drawArrays(int first, int count, int mode) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(first); CV_UNUSED(count); CV_UNUSED(mode); + throw_no_ogl(); +#else + gl::DrawArrays(mode, first, count); +#endif // HAVE_OPENGL +} + +void cv::ogl::drawElements(int first, int count, int type, int mode) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(first); CV_UNUSED(count); CV_UNUSED(type); CV_UNUSED(mode); + throw_no_ogl(); +#else + gl::DrawElements(mode, count, type, reinterpret_cast(static_cast(first))); +#endif +} + +void cv::ogl::enable(int cap) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(cap); + throw_no_ogl(); +#else + gl::Enable(cap); +#endif +} + +void cv::ogl::disable(int cap) +{ +#ifndef HAVE_OPENGL + CV_UNUSED(cap); + throw_no_ogl(); +#else + gl::Disable(cap); +#endif +} + //////////////////////////////////////////////////////////////////////// // CL-GL Interoperability diff --git a/modules/highgui/include/opencv2/highgui.hpp b/modules/highgui/include/opencv2/highgui.hpp index 98761cb9e3..8dc2a9e1d7 100644 --- a/modules/highgui/include/opencv2/highgui.hpp +++ b/modules/highgui/include/opencv2/highgui.hpp @@ -201,6 +201,11 @@ typedef void (*TrackbarCallback)(int pos, void* userdata); */ typedef void (*OpenGlDrawCallback)(void* userdata); +/** @brief Callback function defined to be called when a window's resources should be freed. See cv::setOpenGlFreeCallback +@param userdata The window's OpenGL user data to be freed. + */ +typedef void (*OpenGlFreeCallback)(void* userdata); + /** @brief Callback function for a button created by cv::createButton @param state current state of the button. It could be -1 for a push button, 0 or 1 for a check/radio box button. @param userdata The optional parameter. @@ -617,6 +622,26 @@ prototyped as void Foo(void\*) . */ CV_EXPORTS void setOpenGlDrawCallback(const String& winname, OpenGlDrawCallback onOpenGlDraw, void* userdata = 0); +/** @brief Sets a callback function to be called when OpenGL resources related to a window should be freed. + +@param winname Name of the window. +@param onOpenGlFree Pointer to the function to be called when resources should be freed. This +function should be prototyped as void Foo(void\*) . + */ +CV_EXPORTS void setOpenGlFreeCallback(const String& winname, OpenGlFreeCallback onOpenGlFree); + +/** @brief This function returns the pointer to the function associated to a window to be called every frame. See cv::setOpenGlDrawCallback. + +@param winname Name of the window. + */ +CV_EXPORTS OpenGlDrawCallback getOpenGlDrawCallback(const String& winname); + +/** @brief This function returns the pointer to the OpenGL user data associated to a window. + +@param winname Name of the window. + */ +CV_EXPORTS void* getOpenGlUserData(const String& winname); + /** @brief Sets the specified window as current OpenGL context. @param winname Name of the window. diff --git a/modules/highgui/src/precomp.hpp b/modules/highgui/src/precomp.hpp index d6fab9b0d9..48bede2dfb 100644 --- a/modules/highgui/src/precomp.hpp +++ b/modules/highgui/src/precomp.hpp @@ -116,6 +116,11 @@ void setOpenGLDrawCallbackImpl(const char* window_name, CvOpenGlDrawCallback cal void setOpenGLContextImpl(const char* window_name); void updateWindowImpl(const char* window_name); +typedef void (CV_CDECL *CvOpenGlFreeCallback)(void* userdata); +void setOpenGLFreeCallbackImpl(const char* window_name, CvOpenGlFreeCallback callback); +CvOpenGlDrawCallback getOpenGLDrawCallbackImpl(const char* window_name); +void* getOpenGLUserDataImpl(const char* window_name); + //Yannick Verdie 2010, Max Kostin 2015 void cvSetModeWindow_W32(const char* name, double prop_value); diff --git a/modules/highgui/src/window.cpp b/modules/highgui/src/window.cpp index d29c8547fe..fe0ae0f05f 100644 --- a/modules/highgui/src/window.cpp +++ b/modules/highgui/src/window.cpp @@ -915,6 +915,24 @@ void cv::setOpenGlDrawCallback(const String& name, OpenGlDrawCallback callback, setOpenGLDrawCallbackImpl(name.c_str(), callback, userdata); } +void cv::setOpenGlFreeCallback(const String& name, OpenGlFreeCallback callback) +{ + CV_TRACE_FUNCTION(); + setOpenGLFreeCallbackImpl(name.c_str(), callback); +} + +cv::OpenGlDrawCallback cv::getOpenGlDrawCallback(const String& name) +{ + CV_TRACE_FUNCTION(); + return getOpenGLDrawCallbackImpl(name.c_str()); +} + +void* cv::getOpenGlUserData(const String& name) +{ + CV_TRACE_FUNCTION(); + return getOpenGLUserDataImpl(name.c_str()); +} + void cv::setOpenGlContext(const String& windowName) { CV_TRACE_FUNCTION(); @@ -994,6 +1012,14 @@ void cv::imshow( const String& winname, InputArray _img ) } else { + auto prevCallback = getOpenGlDrawCallback(winname); + + if (prevCallback != nullptr && prevCallback != glDrawTextureCallback) + { + CV_LOG_ERROR(NULL, "OpenCV/UI: can't show image because the OpenGL callback is already being used on window: '" << winname << "'"); + return; + } + const double autoSize = getWindowProperty(winname, WND_PROP_AUTOSIZE); if (autoSize > 0) @@ -1113,6 +1139,21 @@ void updateWindowImpl(const char*) CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support"); } +void setOpenGLFreeCallbackImpl(const char*, CvOpenGlFreeCallback) +{ + CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support"); +} + +CvOpenGlDrawCallback getOpenGLDrawCallbackImpl(const char*) +{ + CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support"); +} + +void* getOpenGLUserDataImpl(const char*) +{ + CV_Error(cv::Error::OpenGlNotSupported, "The library is compiled without OpenGL support"); +} + #endif // !HAVE_OPENGL //========================= Qt fallback ========================= diff --git a/modules/highgui/src/window_QT.cpp b/modules/highgui/src/window_QT.cpp index cf3fbe01d6..dcf196f6d7 100644 --- a/modules/highgui/src/window_QT.cpp +++ b/modules/highgui/src/window_QT.cpp @@ -727,6 +727,53 @@ void setOpenGLDrawCallbackImpl(const char* window_name, CvOpenGlDrawCallback cal } +void setOpenGLFreeCallbackImpl(const char* window_name, CvOpenGlFreeCallback callback) +{ + if (!guiMainThread) + CV_Error( cv::Error::StsNullPtr, "NULL guiReceiver (please create a window)" ); + + QMetaObject::invokeMethod(guiMainThread, + "setOpenGlFreeCallback", + autoBlockingConnection(), + Q_ARG(QString, QString(window_name)), + Q_ARG(void*, (void*)callback)); +} + + +CvOpenGlDrawCallback getOpenGLDrawCallbackImpl(const char* window_name) +{ + if (!guiMainThread) + CV_Error( cv::Error::StsNullPtr, "NULL guiReceiver (please create a window)" ); + + void* callback; + + QMetaObject::invokeMethod(guiMainThread, + "getOpenGlDrawCallback", + autoBlockingConnection(), + Q_RETURN_ARG(void*, callback), + Q_ARG(QString, QString(window_name))); + + return (CvOpenGlDrawCallback)callback; +} + + +void* getOpenGLUserDataImpl(const char* window_name) +{ + if (!guiMainThread) + CV_Error( cv::Error::StsNullPtr, "NULL guiReceiver (please create a window)" ); + + void* data; + + QMetaObject::invokeMethod(guiMainThread, + "getOpenGlUserData", + autoBlockingConnection(), + Q_RETURN_ARG(void*, data), + Q_ARG(QString, QString(window_name))); + + return data; +} + + void setOpenGLContextImpl(const char* window_name) { if (!guiMainThread) @@ -1238,6 +1285,34 @@ void GuiReceiver::setOpenGlDrawCallback(QString name, void* callback, void* user w->setOpenGlDrawCallback((CvOpenGlDrawCallback) callback, userdata); } +void GuiReceiver::setOpenGlFreeCallback(QString name, void* callback) +{ + QPointer w = icvFindWindowByName(name); + + if (w) + w->setOpenGlFreeCallback((CvOpenGlDrawCallback)callback); +} + +void* GuiReceiver::getOpenGlDrawCallback(QString name) +{ + QPointer w = icvFindWindowByName(name); + + if (!w) + return nullptr; + + return reinterpret_cast(w->getOpenGlDrawCallback()); +} + +void* GuiReceiver::getOpenGlUserData(QString name) +{ + QPointer w = icvFindWindowByName(name); + + if (!w) + return nullptr; + + return w->getOpenGlUserData(); +} + void GuiReceiver::setOpenGlContext(QString name) { QPointer w = icvFindWindowByName(name); @@ -1893,6 +1968,24 @@ void CvWindow::setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void* userda } +void CvWindow::setOpenGlFreeCallback(CvOpenGlFreeCallback callback) +{ + myView->setOpenGlFreeCallback(callback); +} + + +CvOpenGlDrawCallback CvWindow::getOpenGlDrawCallback() +{ + return myView->getOpenGlDrawCallback(); +} + + +void* CvWindow::getOpenGlUserData() +{ + return myView->getOpenGlUserData(); +} + + void CvWindow::makeCurrentOpenGlContext() { myView->makeCurrentOpenGlContext(); @@ -2587,6 +2680,24 @@ void DefaultViewPort::setOpenGlDrawCallback(CvOpenGlDrawCallback /*callback*/, v } +void DefaultViewPort::setOpenGlFreeCallback(CvOpenGlFreeCallback /*callback*/) +{ + CV_Error(cv::Error::OpenGlNotSupported, "Window doesn't support OpenGL"); +} + + +CvOpenGlDrawCallback DefaultViewPort::getOpenGlDrawCallback() +{ + CV_Error(cv::Error::OpenGlNotSupported, "Window doesn't support OpenGL"); +} + + +void* DefaultViewPort::getOpenGlUserData() +{ + CV_Error(cv::Error::OpenGlNotSupported, "Window doesn't support OpenGL"); +} + + void DefaultViewPort::makeCurrentOpenGlContext() { CV_Error(cv::Error::OpenGlNotSupported, "Window doesn't support OpenGL"); @@ -3201,11 +3312,15 @@ void DefaultViewPort::setSize(QSize /*size_*/) OpenGlViewPort::OpenGlViewPort(QWidget* _parent) : OpenCVQtWidgetBase(_parent), OCVViewPort(), size(-1, -1) { glDrawCallback = 0; + glFreeCallback = 0; glDrawData = 0; } OpenGlViewPort::~OpenGlViewPort() { + // Fire the free callback so user GL resources are released (parity with GTK/w32). + if (glFreeCallback && glDrawData) + glFreeCallback(glDrawData); } QWidget* OpenGlViewPort::getWidget() @@ -3245,6 +3360,21 @@ void OpenGlViewPort::setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void* glDrawData = userdata; } +void OpenGlViewPort::setOpenGlFreeCallback(CvOpenGlFreeCallback callback) +{ + glFreeCallback = callback; +} + +CvOpenGlDrawCallback OpenGlViewPort::getOpenGlDrawCallback() +{ + return glDrawCallback; +} + +void* OpenGlViewPort::getOpenGlUserData() +{ + return glDrawData; +} + void OpenGlViewPort::makeCurrentOpenGlContext() { makeCurrent(); diff --git a/modules/highgui/src/window_QT.h b/modules/highgui/src/window_QT.h index 884f1162a8..ec7295e8c1 100644 --- a/modules/highgui/src/window_QT.h +++ b/modules/highgui/src/window_QT.h @@ -156,6 +156,9 @@ public slots: void enablePropertiesButtonEachWindow(); void setOpenGlDrawCallback(QString name, void* callback, void* userdata); + void setOpenGlFreeCallback(QString name, void* callback); + void* getOpenGlDrawCallback(QString name); + void* getOpenGlUserData(QString name); void setOpenGlContext(QString name); void updateWindow(QString name); double isOpenGl(QString name); @@ -327,6 +330,9 @@ public: static void addSlider2(CvWindow* w, QString name, int* value, int count, CvTrackbarCallback2 on_change, void* userdata); void setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void* userdata); + void setOpenGlFreeCallback(CvOpenGlFreeCallback callback); + CvOpenGlDrawCallback getOpenGlDrawCallback(); + void* getOpenGlUserData(); void makeCurrentOpenGlContext(); void updateGl(); bool isOpenGl(); @@ -413,6 +419,9 @@ public: virtual void startDisplayInfo(QString text, int delayms) = 0; virtual void setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void* userdata) = 0; + virtual void setOpenGlFreeCallback(CvOpenGlFreeCallback callback) = 0; + virtual CvOpenGlDrawCallback getOpenGlDrawCallback() = 0; + virtual void* getOpenGlUserData() = 0; virtual void makeCurrentOpenGlContext() = 0; virtual void updateGl() = 0; @@ -465,6 +474,9 @@ public: void startDisplayInfo(QString text, int delayms) CV_OVERRIDE; void setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void* userdata) CV_OVERRIDE; + void setOpenGlFreeCallback(CvOpenGlFreeCallback callback) CV_OVERRIDE; + CvOpenGlDrawCallback getOpenGlDrawCallback() CV_OVERRIDE; + void* getOpenGlUserData() CV_OVERRIDE; void makeCurrentOpenGlContext() CV_OVERRIDE; void updateGl() CV_OVERRIDE; @@ -487,6 +499,7 @@ private: QSize size; CvOpenGlDrawCallback glDrawCallback; + CvOpenGlFreeCallback glFreeCallback; void* glDrawData; }; @@ -514,6 +527,10 @@ public: void startDisplayInfo(QString text, int delayms) CV_OVERRIDE; void setOpenGlDrawCallback(CvOpenGlDrawCallback callback, void* userdata) CV_OVERRIDE; + void setOpenGlFreeCallback(CvOpenGlFreeCallback callback) CV_OVERRIDE; + CvOpenGlDrawCallback getOpenGlDrawCallback() CV_OVERRIDE; + void* getOpenGlUserData() CV_OVERRIDE; + void makeCurrentOpenGlContext() CV_OVERRIDE; void updateGl() CV_OVERRIDE; diff --git a/modules/highgui/src/window_gtk.cpp b/modules/highgui/src/window_gtk.cpp index ba08a336b6..ec5df72aa0 100644 --- a/modules/highgui/src/window_gtk.cpp +++ b/modules/highgui/src/window_gtk.cpp @@ -557,7 +557,7 @@ struct CvWindow : CvUIBase last_key(0), flags(0), status(0), on_mouse(NULL), on_mouse_param(NULL) #ifdef HAVE_OPENGL - ,useGl(false), glDrawCallback(NULL), glDrawData(NULL), glArea(NULL) + ,useGl(false), glDrawCallback(NULL), glFreeCallback(NULL), glDrawData(NULL), glArea(NULL) #endif { CV_LOG_INFO(NULL, "OpenCV/UI: creating GTK window: " << window_name); @@ -583,6 +583,7 @@ struct CvWindow : CvUIBase bool useGl; CvOpenGlDrawCallback glDrawCallback; + CvOpenGlFreeCallback glFreeCallback; void* glDrawData; GtkWidget* glArea; #endif @@ -728,8 +729,10 @@ static Rect getImageRect_(const std::shared_ptr& window) gint wx, wy; #ifdef HAVE_OPENGL if (window->useGl) { - gtk_widget_translate_coordinates(window->widget, gtk_widget_get_toplevel(window->widget), 0, 0, &wx, &wy); - return Rect(wx, wy, gtk_widget_get_allocated_width(window->widget), gtk_widget_get_allocated_height(window->widget)); + // OpenGL windows render into the GtkGLArea, not window->widget + GtkWidget* surf = window->glArea ? window->glArea : window->widget; + gtk_widget_translate_coordinates(surf, gtk_widget_get_toplevel(surf), 0, 0, &wx, &wy); + return Rect(wx, wy, gtk_widget_get_allocated_width(surf), gtk_widget_get_allocated_height(surf)); } #endif @@ -846,8 +849,14 @@ double cvGetRatioWindow_GTK(const char* name) static double getRatioWindow_(const std::shared_ptr& window) { +#ifdef HAVE_OPENGL + // OpenGL windows render into the GtkGLArea, not window->widget. + GtkWidget* surf = (window->useGl && window->glArea) ? window->glArea : window->widget; +#else + GtkWidget* surf = window->widget; +#endif double result = static_cast( - gtk_widget_get_allocated_width(window->widget)) / gtk_widget_get_allocated_height(window->widget); + gtk_widget_get_allocated_width(surf)) / gtk_widget_get_allocated_height(surf); return result; } @@ -1085,6 +1094,7 @@ static std::shared_ptr namedWindow_(const std::string& name, int flags createGlContext(window); window->glDrawCallback = 0; + window->glFreeCallback = 0; window->glDrawData = 0; #endif @@ -1118,6 +1128,22 @@ static std::shared_ptr namedWindow_(const std::string& name, int flags gtk_widget_add_events (window->widget, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK) ; #endif //GTK_VERSION3_4 +#if defined(HAVE_OPENGL) && defined(GTK_VERSION3) + // For OpenGL windows the GtkGLArea is the visible widget; deliver mouse events from it. + if ((flags & cv::WINDOW_OPENGL) && window->glArea) + { + g_signal_connect( window->glArea, "button-press-event", G_CALLBACK(icvOnMouse), window ); + g_signal_connect( window->glArea, "button-release-event", G_CALLBACK(icvOnMouse), window ); + g_signal_connect( window->glArea, "motion-notify-event", G_CALLBACK(icvOnMouse), window ); + g_signal_connect( window->glArea, "scroll-event", G_CALLBACK(icvOnMouse), window ); +#if defined(GTK_VERSION3_4) + gtk_widget_add_events( window->glArea, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK ); +#else + gtk_widget_add_events( window->glArea, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK ); +#endif + } +#endif + gtk_widget_show( window->frame ); gtk_window_set_title(GTK_WINDOW(window->frame), name.c_str()); @@ -1226,6 +1252,54 @@ void setOpenGLDrawCallbackImpl(const char* name, CvOpenGlDrawCallback callback, window->glDrawData = userdata; } +void setOpenGLFreeCallbackImpl(const char* name, CvOpenGlFreeCallback callback) +{ + CV_Assert(name && "NULL name string"); + + CV_LOCK_MUTEX(); + + auto window = icvFindWindowByName(name); + if( !window ) + return; + + if (!window->useGl) + CV_Error( cv::Error::OpenGlNotSupported, "Window was created without OpenGL context" ); + + window->glFreeCallback = callback; +} + +CvOpenGlDrawCallback getOpenGLDrawCallbackImpl(const char* name) +{ + CV_Assert(name && "NULL name string"); + + CV_LOCK_MUTEX(); + + auto window = icvFindWindowByName(name); + if (!window) + return NULL; + + if (!window->useGl) + return NULL; + + return window->glDrawCallback; +} + +void* getOpenGLUserDataImpl(const char* name) +{ + CV_Assert(name && "NULL name string"); + + CV_LOCK_MUTEX(); + + auto window = icvFindWindowByName(name); + if( !window ) + return NULL; + + if (!window->useGl) + return NULL; + + return window->glDrawData; +} + #endif // HAVE_OPENGL @@ -1239,6 +1313,12 @@ CvWindow::~CvWindow() inline void CvWindow::destroy() { CV_LOG_INFO(NULL, "OpenCV/UI: destroying GTK window: " << name); +#ifdef HAVE_OPENGL + if (glFreeCallback && glDrawData) { + glFreeCallback(glDrawData); + glDrawData = 0; + } +#endif // HAVE_OPENGL gtk_widget_destroy(frame); frame = nullptr; } @@ -1837,16 +1917,21 @@ static gboolean icvOnMouse( GtkWidget *widget, GdkEvent *event, gpointer user_da // TODO move this logic to CvImageWidget // TODO add try-catch wrappers into all callbacks CvWindow* window = (CvWindow*)user_data; + bool is_gl_area = false; +#if defined(HAVE_OPENGL) && defined(GTK_VERSION3) + is_gl_area = (window && window->glArea && widget == window->glArea); +#endif if (!window || !widget || window->signature != CV_WINDOW_MAGIC_VAL || - window->widget != widget || + (window->widget != widget && !is_gl_area) || !window->on_mouse) return FALSE; Point2f pt32f = {-1., -1.}; Point pt = {-1,-1}; int cv_event = -1, state = 0, flags = 0; - CvImageWidget * image_widget = CV_IMAGE_WIDGET( widget ); + // for OpenGL windows the image state lives on window->widget + CvImageWidget * image_widget = CV_IMAGE_WIDGET( window->widget ); if( event->type == GDK_MOTION_NOTIFY ) { diff --git a/modules/highgui/src/window_w32.cpp b/modules/highgui/src/window_w32.cpp index 194d6cefa0..32ab0cdaba 100644 --- a/modules/highgui/src/window_w32.cpp +++ b/modules/highgui/src/window_w32.cpp @@ -229,6 +229,7 @@ struct CvWindow : public std::enable_shared_from_this HGLRC hGLRC = 0; CvOpenGlDrawCallback glDrawCallback = nullptr; + CvOpenGlFreeCallback glFreeCallback = nullptr; void* glDrawData = nullptr; #endif }; @@ -1084,6 +1085,7 @@ static std::shared_ptr namedWindow_(const std::string& name, int flags } window->glDrawCallback = 0; + window->glFreeCallback = 0; window->glDrawData = 0; #endif @@ -1157,6 +1159,51 @@ void setOpenGLDrawCallbackImpl(const char* name, CvOpenGlDrawCallback callback, window->glDrawData = userdata; } +void setOpenGLFreeCallbackImpl(const char* name, CvOpenGlFreeCallback callback) +{ + AutoLock lock(getWindowMutex()); + + if (!name) + CV_Error(Error::StsNullPtr, "NULL name string"); + + auto window = icvFindWindowByName(name); + if (!window) + CV_Error_(Error::StsNullPtr, ("NULL window: '%s'", name)); + + if (!window->useGl) + CV_Error(Error::OpenGlNotSupported, "Window was created without OpenGL context"); + + window->glFreeCallback = callback; +} + +CvOpenGlDrawCallback getOpenGLDrawCallbackImpl(const char* name) +{ + AutoLock lock(getWindowMutex()); + + if (!name) + CV_Error(Error::StsNullPtr, "NULL name string"); + + auto window = icvFindWindowByName(name); + if (!window || !window->useGl) + return NULL; + + return window->glDrawCallback; +} + +void* getOpenGLUserDataImpl(const char* name) +{ + AutoLock lock(getWindowMutex()); + + if (!name) + CV_Error(Error::StsNullPtr, "NULL name string"); + + auto window = icvFindWindowByName(name); + if (!window || !window->useGl) + return NULL; + + return window->glDrawData; +} + #endif // HAVE_OPENGL static void icvRemoveWindow(const std::shared_ptr& window_) @@ -1179,6 +1226,8 @@ static void icvRemoveWindow(const std::shared_ptr& window_) } #ifdef HAVE_OPENGL + if (window.glFreeCallback && window.glDrawData) + window.glFreeCallback(window.glDrawData); if (window.useGl) releaseGlContext(window); #endif @@ -1800,7 +1849,11 @@ static LRESULT CALLBACK HighGUIProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); +#ifdef HAVE_OPENGL + if ((window.flags & cv::WINDOW_AUTOSIZE) || window.glFreeCallback) +#else if (window.flags & cv::WINDOW_AUTOSIZE) +#endif { // As user can't change window size, do not scale window coordinates. Underlying windowing system // may prevent full window from being displayed and in this case coordinates should not be scaled. diff --git a/modules/highgui/test/test_opengl.cpp b/modules/highgui/test/test_opengl.cpp new file mode 100644 index 0000000000..5528f89211 --- /dev/null +++ b/modules/highgui/test/test_opengl.cpp @@ -0,0 +1,61 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// Copyright (C) 2026, BigVision LLC, all rights reserved. +// Third party copyrights are property of their respective owners. + +#include "test_precomp.hpp" +#include + +namespace opencv_test { namespace { + +using namespace cv; + +// ogl wrappers need a current GL context; skip when none is available (headless CI). +static void ensureGlContext(const String& w) +{ + try + { + namedWindow(w, WINDOW_OPENGL); + if (getWindowProperty(w, WND_PROP_OPENGL) <= 0) + throw cvtest::SkipTestException("window created without an OpenGL context"); + setOpenGlContext(w); + } + catch (const cv::Exception&) + { + throw cvtest::SkipTestException("OpenGL/display not available"); + } +} + +// A grown sole-owner buffer must keep the same GL id (in-place resize). +TEST(OpenGL_Buffer, resize_keeps_id) +{ + const String w = "ogl_buf_test"; + ensureGlContext(w); + + ogl::Buffer buf; + buf.create(4, 3, CV_32F, ogl::Buffer::ARRAY_BUFFER, false); + unsigned int id1 = buf.bufId(); + EXPECT_NE(id1, 0u); + + buf.create(16, 3, CV_32F, ogl::Buffer::ARRAY_BUFFER, false); // grow -> resize in place + EXPECT_EQ(buf.bufId(), id1); + + destroyWindow(w); +} + +// Shader compile + type(). +TEST(OpenGL_Shader, compile_and_type) +{ + const String w = "ogl_shader_test"; + ensureGlContext(w); + + const char* vs = "#version 330 core\nvoid main(){ gl_Position = vec4(0.0); }"; + ogl::Shader sh(vs, ogl::Shader::VERTEX, false); + EXPECT_NE(sh.shaderId(), 0u); + EXPECT_EQ(sh.type(), ogl::Shader::VERTEX); + + destroyWindow(w); +} + +}} // namespace diff --git a/samples/opengl/CMakeLists.txt b/samples/opengl/CMakeLists.txt index 689a5517d9..9ab3892ffe 100644 --- a/samples/opengl/CMakeLists.txt +++ b/samples/opengl/CMakeLists.txt @@ -6,9 +6,6 @@ if(UNIX) find_package(X11 QUIET) endif() -find_package(PkgConfig QUIET) -pkg_search_module(EPOXY QUIET epoxy) - SET(OPENCV_OPENGL_SAMPLES_REQUIRED_DEPS opencv_core opencv_imgproc @@ -26,9 +23,6 @@ if(BUILD_EXAMPLES AND OCV_DEPENDENCIES_FOUND) if(NOT X11_FOUND) ocv_list_filterout(all_samples "opengl_interop") endif() - if(NOT EPOXY_FOUND) - ocv_list_filterout(all_samples "opengl3_2") - endif() foreach(sample_filename ${all_samples}) ocv_define_sample(tgt ${sample_filename} opengl) ocv_target_link_libraries(${tgt} PRIVATE "${OPENGL_LIBRARIES}" "${OPENCV_OPENGL_SAMPLES_REQUIRED_DEPS}") @@ -36,10 +30,6 @@ if(BUILD_EXAMPLES AND OCV_DEPENDENCIES_FOUND) ocv_target_link_libraries(${tgt} PRIVATE ${X11_LIBRARIES}) ocv_target_include_directories(${tgt} ${X11_INCLUDE_DIR}) endif() - if(sample_filename STREQUAL "opengl3_2.cpp") - ocv_target_link_libraries(${tgt} PRIVATE ${EPOXY_LIBRARIES}) - ocv_target_include_directories(${tgt} PRIVATE ${EPOXY_INCLUDE_DIRS}) - endif() endforeach() endif() diff --git a/samples/opengl/opengl3_2.cpp b/samples/opengl/opengl3_2.cpp index 1a8e9a804c..c93ca21df7 100644 --- a/samples/opengl/opengl3_2.cpp +++ b/samples/opengl/opengl3_2.cpp @@ -1,54 +1,36 @@ #include -#include - -#ifdef _WIN32 - #define WIN32_LEAN_AND_MEAN 1 - #define NOMINMAX 1 - #include -#endif - -#if defined(__APPLE__) - #include - #include -#else - #include - #include -#endif - #include "opencv2/core.hpp" #include "opencv2/core/opengl.hpp" -#include "opencv2/core/cuda.hpp" #include "opencv2/highgui.hpp" using namespace std; using namespace cv; -using namespace cv::cuda; const int win_width = 800; const int win_height = 640; +// All GL objects live for the lifetime of the window's GL context. +// autoRelease is left at its default (false) so they are reclaimed with the +// context on shutdown; calling glDelete* from a destructor after the context +// is gone would raise a GL error (see cv::ogl wrapper docs). struct DrawData { - GLuint vao, vbo, program, textureID; + ogl::Program program; + ogl::Buffer vbo; // referenced by the VertexArray; must outlive it + ogl::VertexArray vao; + ogl::Texture2D tex; + int transformLoc; }; -static cv::Mat rot(float angle) +static cv::Matx44f rot(float angle) { - cv::Mat R_y = (cv::Mat_(4,4) << + // Row-major; ogl::Program::setUniformMat4x4 uploads with transpose enabled. + return cv::Matx44f( cos(angle), 0, sin(angle), 0, 0, 1, 0, 0, -sin(angle), 0, cos(angle), 0, 0, 0, 0, 1); - - return R_y; -} - -static GLuint create_shader(const char* source, GLenum type) { - GLuint shader = glCreateShader(type); - glShaderSource(shader, 1, &source, NULL); - glCompileShader(shader); - return shader; } static void draw(void* userdata) { @@ -56,17 +38,17 @@ static void draw(void* userdata) { static float angle = 0.0f; angle += 1.f; - cv::Mat trans = rot(CV_PI * angle / 360.f); + cv::Matx44f trans = rot(CV_PI * angle / 360.f); - glClearColor(0.0, 0.0, 0.0, 1.0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + ogl::clearColor(Scalar::all(0)); + ogl::clearDepth(1.0f); - glUseProgram(data->program); - glUniformMatrix4fv(glGetUniformLocation(data->program, "transform"), 1, GL_FALSE, trans.ptr()); - glBindTexture(GL_TEXTURE_2D, data->textureID); - glBindVertexArray(data->vao); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glBindVertexArray(0); + data->program.bind(); + ogl::Program::setUniformMat4x4(data->transformLoc, trans); + data->tex.bind(); + data->vao.bind(); + ogl::drawArrays(0, 4, ogl::TRIANGLE_STRIP); + ogl::VertexArray::unbind(); } int main(int argc, char* argv[]) @@ -93,7 +75,8 @@ int main(int argc, char* argv[]) DrawData data; - glEnable(GL_DEPTH_TEST); + ogl::enable(ogl::DEPTH_TEST); + const char *vertex_shader_source = "#version 330 core\n" "layout (location = 0) in vec3 position;\n" @@ -112,15 +95,14 @@ int main(int argc, char* argv[]) "void main() {\n" " color = texture(ourTexture, TexCoord);\n" "}\n"; - data.program = glCreateProgram(); - GLuint vertex_shader = create_shader(vertex_shader_source, GL_VERTEX_SHADER); - GLuint fragment_shader = create_shader(fragment_shader_source, GL_FRAGMENT_SHADER); - glAttachShader(data.program, vertex_shader); - glAttachShader(data.program, fragment_shader); - glLinkProgram(data.program); - glUseProgram(data.program); - GLfloat vertices[] = { + // Compile shaders and link the program via the ogl wrappers. + ogl::Shader vertex_shader(vertex_shader_source, ogl::Shader::VERTEX); + ogl::Shader fragment_shader(fragment_shader_source, ogl::Shader::FRAGMENT); + data.program = ogl::Program(vertex_shader, fragment_shader); + data.transformLoc = data.program.getUniformLocation("transform"); + + const float vertices[] = { // Positions // Texture Coords 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // Top Right 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, // Bottom Right @@ -128,30 +110,36 @@ int main(int argc, char* argv[]) -1.0f, -1.0f, 0.0f, 0.0f, 0.0f // Bottom Left }; - glGenVertexArrays(1, &data.vao); - glGenBuffers(1, &data.vbo); - glBindVertexArray(data.vao); - glBindBuffer(GL_ARRAY_BUFFER, data.vbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + // Upload the interleaved vertex data into a GL buffer. + Mat vertexMat(4, 5, CV_32F, (void*)vertices); + data.vbo = ogl::Buffer(vertexMat, ogl::Buffer::ARRAY_BUFFER); - // Position attribute - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0); - glEnableVertexAttribArray(0); - // Texture Coord attribute - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat))); - glEnableVertexAttribArray(1); - glBindVertexArray(0); // Unbind VAO + // Position attribute (location 0): 3 floats, no offset. + ogl::Attribute posAttr; + posAttr.buffer_ = data.vbo; + posAttr.stride_ = 5 * sizeof(float); + posAttr.offset_ = 0; + posAttr.size_ = 3; + posAttr.type_ = ogl::Attribute::FLOAT; + posAttr.integer_ = false; + posAttr.normalized_ = false; + posAttr.shader_loc_ = 0; + // Texture coord attribute (location 1): 2 floats, offset past the 3 position floats. + ogl::Attribute texAttr; + texAttr.buffer_ = data.vbo; + texAttr.stride_ = 5 * sizeof(float); + texAttr.offset_ = 3 * sizeof(float); + texAttr.size_ = 2; + texAttr.type_ = ogl::Attribute::FLOAT; + texAttr.integer_ = false; + texAttr.normalized_ = false; + texAttr.shader_loc_ = 1; -// Image to texture - glGenTextures(1, &data.textureID); - glBindTexture(GL_TEXTURE_2D, data.textureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.cols, img.rows, 0, GL_BGR, GL_UNSIGNED_BYTE, img.data); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glBindTexture(GL_TEXTURE_2D, 0); + data.vao = ogl::VertexArray({posAttr, texAttr}); + + // Image to texture. + data.tex = ogl::Texture2D(img); setOpenGlDrawCallback("OpenGL", draw, &data);