diff --git a/libs/gfx/include/psemek/gfx/mesh.hpp b/libs/gfx/include/psemek/gfx/mesh.hpp index ddbdc418..13a7af77 100644 --- a/libs/gfx/include/psemek/gfx/mesh.hpp +++ b/libs/gfx/include/psemek/gfx/mesh.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -18,7 +19,9 @@ namespace psemek::gfx template struct normalized - {}; + { + using type = T; + }; // skips a single attribute index struct skip @@ -31,6 +34,13 @@ namespace psemek::gfx static constexpr std::size_t size = N; }; + // instance attribute, for instanced meshes only + template + struct instanced + { + using type = T; + }; + template struct attrib_traits; @@ -154,6 +164,16 @@ namespace psemek::gfx static constexpr GLboolean normalized = gl::TRUE_; }; + template + struct attrib_traits> + { + using attrib_type = T; + + static constexpr GLint size = attrib_traits::size; + static constexpr GLenum type = attrib_traits::type; + static constexpr GLboolean normalized = attrib_traits::normalized; + }; + namespace detail { @@ -165,6 +185,34 @@ namespace psemek::gfx struct is_padding> : std::true_type {}; + template + struct is_instanced : std::false_type + {}; + + template + struct is_instanced> : std::true_type + {}; + + template + struct remove_instanced + { + using type = T; + }; + + template + struct remove_instanced> + { + using type = T; + }; + + template + struct is_matrix : std::false_type + {}; + + template + struct is_matrix> : std::true_type + {}; + template std::size_t attr_size() { @@ -182,11 +230,37 @@ namespace psemek::gfx } } - template - struct mesh_setup; + template + struct stride_helper; - template <> - struct mesh_setup<> + template + struct stride_helper + { + static std::size_t stride() + { + return 0; + } + }; + + template + struct stride_helper + { + static std::size_t stride() + { + std::size_t s = 0; + if constexpr (is_instanced::value == InstanceSetup) + { + s = attr_size(); + } + return s + stride_helper::stride(); + } + }; + + template + struct mesh_setup_base; + + template + struct mesh_setup_base { static std::size_t setup() { @@ -197,12 +271,12 @@ namespace psemek::gfx {} }; - template - struct mesh_setup + template + struct mesh_setup_base { static std::size_t setup() { - std::size_t const stride = attr_size() + (0 + ... + attr_size()); + std::size_t const stride = stride_helper::stride(); setup_impl(0, 0, stride); return stride; } @@ -211,24 +285,74 @@ namespace psemek::gfx { if constexpr (std::is_same_v) { - mesh_setup::setup_impl(index + 1, offset, stride); + mesh_setup_base::setup_impl(index + 1, offset, stride); } else if constexpr (is_padding::value) { - mesh_setup::setup_impl(index, offset + Attr1::size, stride); + mesh_setup_base::setup_impl(index, offset + Attr1::size, stride); + } + else if constexpr (!IsInstanced && is_instanced::value) + { + static_assert("cannot use instanced attribute for non-instanced mesh"); + } + else if constexpr (!IsInstanced || (is_instanced::value == InstanceSetup)) + { + using attr = typename remove_instanced::type; + + if constexpr (is_matrix::value) + { + using T = typename attr::scalar_type; + using traits = attrib_traits>; + + for (std::size_t row = 0; row < attr::rows; ++row) + { + gl::EnableVertexAttribArray(index + row); + gl::VertexAttribPointer(index + row, traits::size, traits::type, traits::normalized, stride, reinterpret_cast(offset + row * attr::columns * sizeof(T))); + + if (InstanceSetup) + gl::VertexAttribDivisor(index + row, 1); + } + + mesh_setup_base::setup_impl(index + attr::rows, offset + attr_size(), stride); + } + else + { + if (InstanceSetup) + gl::VertexAttribDivisor(index, 1); + + using traits = attrib_traits; + + gl::EnableVertexAttribArray(index); + gl::VertexAttribPointer(index, traits::size, traits::type, traits::normalized, stride, reinterpret_cast(offset)); + + mesh_setup_base::setup_impl(index + 1, offset + attr_size(), stride); + } } else { - using traits = attrib_traits; + using attr = typename remove_instanced::type; - gl::EnableVertexAttribArray(index); - gl::VertexAttribPointer(index, traits::size, traits::type, traits::normalized, stride, reinterpret_cast(offset)); - - mesh_setup::setup_impl(index + 1, offset + attr_size(), stride); + if constexpr (is_matrix::value) + { + mesh_setup_base::setup_impl(index + attr::rows, offset, stride); + } + else + { + mesh_setup_base::setup_impl(index + 1, offset, stride); + } } } }; + template + using mesh_setup = mesh_setup_base; + + template + using instanced_mesh_vertex_setup = mesh_setup_base; + + template + using instanced_mesh_instance_setup = mesh_setup_base; + template struct gl_type; @@ -496,4 +620,150 @@ namespace psemek::gfx indexed_mesh(int); }; + struct instanced_mesh + { + static instanced_mesh null(); + + instanced_mesh(); + instanced_mesh(instanced_mesh &&); + instanced_mesh(instanced_mesh const &) = delete; + + instanced_mesh & operator = (instanced_mesh &&); + instanced_mesh & operator = (instanced_mesh const &) = delete; + + ~instanced_mesh(); + + template + void setup() + { + gl::BindVertexArray(array_); + + gl::BindBuffer(gl::ARRAY_BUFFER, buffer_); + stride_ = detail::instanced_mesh_vertex_setup::setup(); + + gl::BindBuffer(gl::ARRAY_BUFFER, instance_buffer_); + instance_stride_ = detail::instanced_mesh_instance_setup::setup(); + } + + template + void load(Vertex const * vertices, std::size_t count, GLenum primitive_type, GLenum usage = gl::STREAM_DRAW) + { + if (sizeof(Vertex) != stride_) + throw std::runtime_error("Vertex size not equal to sum of attribute sizes"); + + switch (primitive_type) + { + case gl::LINES: + if ((count % 2) != 0) throw std::runtime_error("Vertex count for GL_LINES should be a multiple of 2"); + break; + case gl::TRIANGLES: + if ((count % 3) != 0) throw std::runtime_error("Vertex count for GL_TRIANGLES should be a multiple of 3"); + break; + default: + break; + } + + gl::BindBuffer(gl::ARRAY_BUFFER, buffer_); + gl::BufferData(gl::ARRAY_BUFFER, count * sizeof(Vertex), vertices, usage); + + count_ = count; + primitive_type_ = primitive_type; + } + + template + void load(std::vector const & vertices, GLenum primitive_type, GLenum usage = gl::STREAM_DRAW) + { + load(vertices.data(), vertices.size(), primitive_type, usage); + } + + template + void load(geom::simplex const * simplices, std::size_t count, GLenum usage = gl::STREAM_DRAW) + { + static_assert(sizeof(geom::simplex) == (N + 1) * sizeof(Vertex)); + + GLenum primitive_type; + + if constexpr (N == 0) + { + primitive_type = gl::POINTS; + } + else if constexpr (N == 1) + { + primitive_type = gl::LINES; + } + else if constexpr (N == 2) + { + primitive_type = gl::TRIANGLES; + } + else + { + static_assert("unknown primitive type"); + } + + load(reinterpret_cast(simplices), count * (N + 1), primitive_type, usage); + } + + template + void load(std::vector> const & simplices, GLenum usage = gl::STREAM_DRAW) + { + load(simplices.data(), simplices.size(), usage); + } + + template + void load_instance(Instance const * instances, std::size_t count, GLenum usage = gl::STREAM_DRAW) + { + if (sizeof(Instance) != instance_stride_) + throw std::runtime_error("Instance size not equal to sum of instanced attribute sizes"); + + gl::BindBuffer(gl::ARRAY_BUFFER, instance_buffer_); + gl::BufferData(gl::ARRAY_BUFFER, count * sizeof(Instance), instances, usage); + instance_count_ = count; + } + + template + void load_instance(std::vector const & instances, GLenum usage = gl::STREAM_DRAW) + { + load_instance(instances.data(), instances.size(), usage); + } + + void draw() const + { + if (count_ == 0) return; + if (instance_count_ == 0) return; + + gl::BindVertexArray(array_); + gl::DrawArraysInstanced(primitive_type_, 0, count_, instance_count_); + } + + GLsizei count() const + { + return count_; + } + + GLsizei instance_count() const + { + return count_; + } + + GLenum primitive_type() const + { + return primitive_type_; + } + + private: + GLuint array_; + GLuint buffer_; + GLuint instance_buffer_; + + GLsizei count_ = 0; + GLsizei instance_count_ = 0; + + std::size_t stride_ = 0; + std::size_t instance_stride_ = 0; + + GLenum primitive_type_; + + instanced_mesh(int); + }; + } diff --git a/libs/gfx/source/mesh.cpp b/libs/gfx/source/mesh.cpp index 56607dea..843be4f6 100644 --- a/libs/gfx/source/mesh.cpp +++ b/libs/gfx/source/mesh.cpp @@ -137,5 +137,79 @@ namespace psemek::gfx index_buffer_ = 0; } + instanced_mesh instanced_mesh::null() + { + return instanced_mesh(0); + } + + instanced_mesh::instanced_mesh() + { + gl::GenVertexArrays(1, &array_); + gl::GenBuffers(1, &buffer_); + gl::GenBuffers(1, &instance_buffer_); + } + + instanced_mesh::instanced_mesh(instanced_mesh && other) + { + array_ = other.array_; + buffer_ = other.buffer_; + instance_buffer_ = other.instance_buffer_; + count_ = other.count_; + instance_count_ = other.instance_count_; + stride_ = other.stride_; + instance_stride_ = other.instance_stride_; + primitive_type_ = other.primitive_type_; + + other.array_ = 0; + other.buffer_ = 0; + other.instance_buffer_ = 0; + other.count_ = 0; + other.instance_count_ = 0; + other.stride_ = 0; + other.instance_stride_ = 0; + } + + instanced_mesh & instanced_mesh::operator = (instanced_mesh && other) + { + if (this == &other) + return *this; + + gl::DeleteVertexArrays(1, &array_); + gl::DeleteBuffers(1, &buffer_); + gl::DeleteBuffers(1, &instance_buffer_); + + array_ = other.array_; + buffer_ = other.buffer_; + instance_buffer_ = other.instance_buffer_; + count_ = other.count_; + instance_count_ = other.instance_count_; + stride_ = other.stride_; + instance_stride_ = other.instance_stride_; + primitive_type_ = other.primitive_type_; + + other.array_ = 0; + other.buffer_ = 0; + other.instance_buffer_ = 0; + other.count_ = 0; + other.instance_count_ = 0; + other.stride_ = 0; + other.instance_stride_ = 0; + + return *this; + } + + instanced_mesh::~instanced_mesh() + { + gl::DeleteVertexArrays(1, &array_); + gl::DeleteBuffers(1, &buffer_); + gl::DeleteBuffers(1, &instance_buffer_); + } + + instanced_mesh::instanced_mesh(int) + { + array_ = 0; + buffer_ = 0; + instance_buffer_ = 0; + } }