// Copyright (c) 2010-2022, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-443271. // // This file is part of the GLVis visualization tool and library. For more // information and source code availability see https://glvis.org. // // GLVis is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. #ifndef GLVIS_TYPES_HPP #define GLVIS_TYPES_HPP #include #include #include #include #include #include #ifdef __INTEL_COMPILER // for some reason, icpc is choking on GLM's constexpr stuff - force it to use // only C++98 features as a workaround. #define GLM_FORCE_CXX98 #endif #include #include #include #include #include #include #include "platform_gl.hpp" using namespace std; class VisualizationScene; namespace gl3 { namespace resource { // RAII scope guard for OpenGL handles. template class Handle { GLuint hnd; public: Handle() : hnd{0} {} Handle(GLuint h) : hnd{h} {} ~Handle() { if (hnd) { GLFinalizer(hnd); } } Handle(Handle&& other) : hnd{other.hnd} { other.hnd = 0; } Handle& operator = (Handle&& other) noexcept { if (this != &other) { hnd = other.hnd; other.hnd = 0; } return *this; } operator GLuint() const { return hnd; } }; inline void boCleanup(GLuint vbo_hnd) { glDeleteBuffers(1, &vbo_hnd); } inline void dspListCleanup(GLuint dlist) { glDeleteLists(dlist, 1); } inline void prgmCleanup(GLuint prgm) { glDeleteProgram(prgm); } inline void shdrCleanup(GLuint shdr) { glDeleteShader(shdr); } inline void vaoCleanup(GLuint vao) { glDeleteVertexArrays(1, &vao); } inline void texCleanup(GLuint tex) { glDeleteTextures(1, &tex); } inline void fboCleanup(GLuint fbo) { glDeleteFramebuffers(1, &fbo); } inline void rboCleanup(GLuint rbo) { glDeleteRenderbuffers(1, &rbo); } using BufObjHandle = Handle; using DispListHandle = Handle; using VtxArrayHandle = Handle; using ShaderPrgmHandle = Handle; using ShaderHandle = Handle; using TextureHandle = Handle; using FBOHandle = Handle; using RenderBufHandle = Handle; } // end namespace resource struct GlMatrix { glm::mat4 mtx; /// Applies a rotation transform to the matrix. void rotate(float angle, double x, double y, double z) { mtx = glm::rotate(mtx, glm::radians(angle), glm::vec3(x,y,z)); } void mult(glm::mat4 rhs) { mtx = mtx * rhs; } /// Applies a translation transform to the matrix. void translate(double x, double y, double z) { mtx = glm::translate(mtx, glm::vec3(x, y, z)); } /// Applies a scale transform to the matrix. void scale(double x, double y, double z) { mtx = glm::scale(mtx, glm::vec3(x, y, z)); } /// Sets the matrix to an orthographic projection. void ortho(double left, double right, double bottom, double top, double z_near, double z_far) { mtx = glm::ortho(left, right, bottom, top, z_near, z_far); } /// Sets the matrix to a perspective projection. void perspective(double fov, double aspect, double z_near, double z_far) { mtx = glm::perspective(glm::radians(fov), aspect, z_near, z_far); } /// Sets the matrix to the identity matrix. void identity() { mtx = glm::mat4(1.0); } }; enum array_layout { LAYOUT_VTX = 0, LAYOUT_VTX_NORMAL, LAYOUT_VTX_COLOR, LAYOUT_VTX_TEXTURE0, LAYOUT_VTX_NORMAL_COLOR, LAYOUT_VTX_NORMAL_TEXTURE0, NUM_LAYOUTS }; inline std::array ColorU8(float rgba[]) { return { (rgba[0] >= 1.0) ? (uint8_t) 255 : (uint8_t)(rgba[0] * 256.), (rgba[1] >= 1.0) ? (uint8_t) 255 : (uint8_t)(rgba[1] * 256.), (rgba[2] >= 1.0) ? (uint8_t) 255 : (uint8_t)(rgba[2] * 256.), (rgba[3] >= 1.0) ? (uint8_t) 255 : (uint8_t)(rgba[3] * 256.), }; } inline std::array ColorU8(float r, float g, float b, float a) { return { (r >= 1.0) ? (uint8_t) 255 : (uint8_t)(r * 256.), (g >= 1.0) ? (uint8_t) 255 : (uint8_t)(g * 256.), (b >= 1.0) ? (uint8_t) 255 : (uint8_t)(b * 256.), (a >= 1.0) ? (uint8_t) 255 : (uint8_t)(a * 256.), }; } struct alignas(16) Vertex { std::array coord; static Vertex create(double * d) { return Vertex {(float) d[0], (float) d[1], (float) d[2]}; } static const int layout = LAYOUT_VTX; }; struct alignas(16) VertexColor { std::array coord; std::array color; static const int layout = LAYOUT_VTX_COLOR; }; struct alignas(16) VertexTex { std::array coord; std::array texCoord; static const int layout = LAYOUT_VTX_TEXTURE0; }; struct alignas(16) VertexNorm { std::array coord; std::array norm; static const int layout = LAYOUT_VTX_NORMAL; }; struct alignas(16) VertexNormColor { std::array coord; std::array norm; std::array color; static const int layout = LAYOUT_VTX_NORMAL_COLOR; }; struct alignas(16) VertexNormTex { std::array coord; std::array norm; std::array texCoord; static const int layout = LAYOUT_VTX_NORMAL_TEXTURE0; }; class GlDrawable; /// Crude fixed-function OpenGL emulation helper. class GlBuilder { GlDrawable * parent_buf; GLenum render_as; int count; bool is_line; bool use_norm; bool use_color; bool use_tex; struct FFState { std::array coords; std::array norm; std::array color; std::array texcoord; }; FFState saved[3]; FFState curr; void saveVertex(const FFState& v); public: GlBuilder(GlDrawable * buf) : parent_buf(buf) , count(0) , is_line(false) , use_norm(false) , use_color(false) , use_tex(false) { } void glBegin(GLenum e) { if (e == GL_LINES || e == GL_LINE_STRIP || e == GL_LINE_LOOP) { is_line = true; } else { is_line = false; } render_as = e; count = 0; } void glEnd() { // create degenerate primitives if necessary if (render_as == GL_LINES && count % 2 != 0) { saveVertex(curr); } if (render_as == GL_TRIANGLES) { for (int i = 0; i < count % 3; i++) { saveVertex(curr); } } if (render_as == GL_LINE_LOOP && count > 2) { // link first and last vertex saveVertex(saved[0]); saveVertex(saved[1]); } count = 0; } void glVertex3d(double x, double y, double z) { curr.coords = { (float) x, (float) y, (float) z }; if (render_as == GL_LINES || render_as == GL_TRIANGLES) { // Lines and triangles are stored as-is saveVertex(curr); } else if (is_line) { // LINE_LOOP and LINE_STRIP: each vertex creates a new line with the // previous vertex if (count == 0) { saved[0] = curr; } else { saveVertex(saved[1]); saveVertex(curr); } saved[1] = curr; } else if (render_as == GL_QUADS) { if (count % 4 == 3) { // split on 0-2 diagonal saveVertex(saved[0]); saveVertex(saved[1]); saveVertex(saved[2]); saveVertex(saved[0]); saveVertex(saved[2]); saveVertex(curr); count -= 4; } else { saved[count] = curr; } } else if (render_as != GL_NONE) { // TriangleStrip, TriangleFan, Polygon if (count >= 2) { saveVertex(saved[0]); saveVertex(saved[1]); saveVertex(curr); if (render_as == GL_TRIANGLE_STRIP) { // pop earliest vertex saved[0] = saved[1]; } saved[1] = curr; } else { saved[count] = curr; } } count++; } void glVertex3dv(const double * d) { glVertex3d(d[0], d[1], d[2]); } void glNormal3d(double nx, double ny, double nz) { use_norm = true; curr.norm = { (float) nx, (float) ny, (float) nz }; } void glNormal3dv(const double * d) { glNormal3d(d[0], d[1], d[2]); } void glColor4f(float r, float g, float b, float a) { if (count == 0) { use_color = true; use_tex = false; } curr.color = { (r >= 1.0) ? (uint8_t) 255 : (uint8_t)(r * 256.), (g >= 1.0) ? (uint8_t) 255 : (uint8_t)(g * 256.), (b >= 1.0) ? (uint8_t) 255 : (uint8_t)(b * 256.), (a >= 1.0) ? (uint8_t) 255 : (uint8_t)(a * 256.), }; } void glColor3f(float r, float g, float b) { glColor4f(r, g, b, 1.f); } void glColor4fv(float * cv) { glColor4f(cv[0], cv[1], cv[2], cv[3]); } void glTexCoord2f(float coord_u, float coord_v) { if (count == 0) { use_tex = true; use_color = false; } curr.texcoord = { coord_u, coord_v }; } }; class IVertexBuffer { private: int handle; public: IVertexBuffer() : handle(0) { } virtual ~IVertexBuffer() { } int getHandle() const { return handle; } void setHandle(int dev_hnd) { handle = dev_hnd; } /// Clears the data stored in the vertex buffer. virtual void clear() = 0; /// Gets the number of vertices contained in the buffer. virtual size_t count() const = 0; /// Gets the primitive type contained by the vertex buffer. virtual GLenum getShape() const = 0; /// Gets the stride between vertices. virtual size_t getStride() const = 0; virtual const void* getData() const = 0; }; template class VertexBuffer : public IVertexBuffer { private: GLenum primitive; std::vector vertex_data; public: typedef typename std::vector::const_iterator ConstIterator; VertexBuffer(GLenum shape) : primitive(shape) { } ~VertexBuffer() { } virtual void clear() { vertex_data.clear(); } virtual size_t count() const { return vertex_data.size(); } virtual GLenum getShape() const { return primitive; } virtual size_t getStride() const { return sizeof(T); } ConstIterator begin() const { return vertex_data.begin(); }; ConstIterator end() const { return vertex_data.end(); }; virtual const void* getData() const { return vertex_data.data(); } /// Add a vertex to the buffer. void addVertex(const T& vertex) { vertex_data.emplace_back(vertex); } /// Add vertices to a buffer. void addVertices(const std::vector& verts) { vertex_data.insert(vertex_data.end(), verts.begin(), verts.end()); } }; class IIndexedBuffer : public IVertexBuffer { public: virtual const std::vector& getIndices() const = 0; }; template class IndexedVertexBuffer : public IIndexedBuffer { private: GLenum primitive; std::vector vertex_data; std::vector vertex_indices; public: typedef typename std::vector::const_iterator ConstIterator; IndexedVertexBuffer(GLenum shape) : primitive(shape) { } ~IndexedVertexBuffer() { } virtual void clear() { vertex_data.clear(); vertex_indices.clear(); } virtual size_t count() const { return vertex_data.size(); } virtual GLenum getShape() const { return primitive; } virtual size_t getStride() const { return sizeof(T); } ConstIterator begin() const { return vertex_data.begin(); }; ConstIterator end() const { return vertex_data.end(); }; virtual const void* getData() const { return vertex_data.data(); } const std::vector& getIndices() const { return vertex_indices; } void addVertices(const std::vector& verts, const std::vector& ids) { int index_offset = vertex_data.size(); std::copy(verts.begin(), verts.end(), std::back_inserter(vertex_data)); int old_end = vertex_indices.size(); std::copy(ids.begin(), ids.end(), std::back_inserter(vertex_indices)); for (size_t i = old_end; i < vertex_indices.size(); i++) { vertex_indices[i] += index_offset; } } }; class TextBuffer : public IVertexBuffer { public: struct Entry { float rx, ry, rz; int ox, oy; std::string text; Entry() = default; Entry(float x, float y, float z, int ox, int oy, const std::string& txt) : rx(x), ry(y), rz(z), ox(ox), oy(oy), text(txt) { } }; typedef std::vector::iterator Iterator; typedef std::vector::const_iterator ConstIterator; private: std::vector vertex_data; size_t num_chars; public: TextBuffer() : num_chars(0) { } ~TextBuffer() { } /// Adds a text element at the specified local space (pre-transform) /// coordinates. void addText(float x, float y, float z, int ox, int oy, const std::string& text) { vertex_data.emplace_back(x, y, z, ox, oy, text); num_chars += text.length(); } /// Gets an iterator referencing the first text entry. Iterator begin() { return vertex_data.begin(); } ConstIterator begin() const { return vertex_data.begin(); } /// Gets an iterator pointing to the last text entry. Iterator end() { return vertex_data.end(); } ConstIterator end() const { return vertex_data.end(); } virtual void clear() { vertex_data.clear(); num_chars = 0; } virtual size_t count() const { return num_chars * 6; }; virtual GLenum getShape() const { return GL_TRIANGLES; }; virtual size_t getStride() const { return sizeof(float) * 8; }; virtual const void* getData() const { return nullptr; } }; class GlDrawable { private: const static size_t NUM_SHAPES = 2; std::unique_ptr buffers[NUM_LAYOUTS][NUM_SHAPES]; std::unique_ptr indexed_buffers[NUM_LAYOUTS][NUM_SHAPES]; TextBuffer text_buffer; friend class GlBuilder; friend class MeshRenderer; friend class ::VisualizationScene; // needed for glTF export template VertexBuffer * getBuffer(GLenum shape) { int idx = -1; if (shape == GL_LINES) { idx = 0; } else if (shape == GL_TRIANGLES) { idx = 1; } else { return nullptr; } if (!buffers[Vert::layout][idx]) { buffers[Vert::layout][idx].reset(new VertexBuffer(shape)); } VertexBuffer * buf = static_cast*> (buffers[Vert::layout][idx].get()); return buf; } template IndexedVertexBuffer * getIndexedBuffer(GLenum shape) { int idx = -1; if (shape == GL_LINES) { idx = 0; } else if (shape == GL_TRIANGLES) { idx = 1; } else { return nullptr; } if (!indexed_buffers[Vert::layout][idx]) { indexed_buffers[Vert::layout][idx] .reset(new IndexedVertexBuffer(shape)); } return static_cast*> (indexed_buffers[Vert::layout][idx].get()); } public: /// Adds a string at the given position in object coordinates. void addText(float x, float y, float z, const std::string& text) { text_buffer.addText(x, y, z, 0, 0, text); } /** @brief Adds a string at the given position in object coordinates with an offset in pixels. */ void addText(float x, float y, float z, int ox, int oy, const std::string& text) { text_buffer.addText(x, y, z, ox, oy, text); } template void addLine(const Vert& v1, const Vert& v2) { getBuffer(GL_LINES)->addVertex(v1); getBuffer(GL_LINES)->addVertex(v2); } template void addLines(const std::vector& verts) { getBuffer(GL_LINES)->addVertices(verts); } template void addTriangle(const Vert& v1, const Vert& v2, const Vert& v3) { getBuffer(GL_TRIANGLES)->addVertex(v1); getBuffer(GL_TRIANGLES)->addVertex(v2); getBuffer(GL_TRIANGLES)->addVertex(v3); } template void addQuad(const Vert& v1, const Vert& v2, const Vert& v3, const Vert& v4) { getBuffer(GL_TRIANGLES)->addVertex(v1); getBuffer(GL_TRIANGLES)->addVertex(v2); getBuffer(GL_TRIANGLES)->addVertex(v3); getBuffer(GL_TRIANGLES)->addVertex(v1); getBuffer(GL_TRIANGLES)->addVertex(v3); getBuffer(GL_TRIANGLES)->addVertex(v4); } template void addTriangleIndexed(const std::vector& verts, const std::vector& inds) { getIndexedBuffer(GL_TRIANGLES)->addVertices(verts, inds); } template void addQuadIndexed(const std::vector& verts, const std::vector& inds) { std::vector new_inds; for (size_t i = 0; i < inds.size() / 4; i++) { new_inds.emplace_back(inds[4*i]); new_inds.emplace_back(inds[4*i + 1]); new_inds.emplace_back(inds[4*i + 2]); new_inds.emplace_back(inds[4*i]); new_inds.emplace_back(inds[4*i + 2]); new_inds.emplace_back(inds[4*i + 3]); } getIndexedBuffer(GL_TRIANGLES)->addVertices(verts, new_inds); } void addCone(float x, float y, float z, float vx, float vy, float vz, float cone_scale = 0.075); GlBuilder createBuilder() { return GlBuilder(this); } /// Clears the drawable object. void clear() { for (int i = 0; i < NUM_LAYOUTS; i++) { for (size_t j = 0; j < NUM_SHAPES; j++) { if (buffers[i][j]) { buffers[i][j]->clear(); } if (indexed_buffers[i][j]) { indexed_buffers[i][j]->clear(); } } } text_buffer.clear(); } }; } #endif // GLVIS_TYPES_HPP