#include #include #include #include namespace psemek::gfx { mesh::mesh(mesh && other) : array_{std::move(other.array_)} , vertex_buffer_{std::move(other.vertex_buffer_)} , index_buffer_{std::move(other.index_buffer_)} , instance_buffer_{std::move(other.instance_buffer_)} , info_{other.info_} { other.info_ = mesh_info{}; } mesh & mesh::operator = (mesh && other) { if (this == &other) return *this; array_ = std::move(other.array_); vertex_buffer_ = std::move(other.vertex_buffer_); index_buffer_ = std::move(other.index_buffer_); instance_buffer_ = std::move(other.instance_buffer_); info_ = other.info_; other.info_ = mesh_info{}; return *this; } void mesh::bind() const { array_.bind(); } void mesh::setup(attribs_description const & attribs) { assert(!attribs.attribs.empty()); bind(); for (std::size_t i = 0; i < info_.max_attribute_index_; ++i) gl::DisableVertexAttribArray(i); std::size_t max_index = 0; bool instanced = false; for (auto const & a : attribs.attribs) { max_index = std::max(max_index, a.index); if (a.divisor != 0) { assert(a.divisor == 1); instanced = true; } } info_.max_attribute_index_ = max_index; info_.stride_ = attribs.vertex_size; info_.instance_stride_ = attribs.instance_size; info_.instanced_ = instanced; if (!vertex_buffer_) vertex_buffer_ = buffer{}; vertex_buffer_.bind(); for (auto const & a : attribs.attribs) { if (a.divisor != 0) continue; gl::EnableVertexAttribArray(a.index); if (a.integer) gl::VertexAttribIPointer(a.index, a.size, a.type, attribs.vertex_size, a.pointer); else gl::VertexAttribPointer(a.index, a.size, a.type, a.normalized, attribs.vertex_size, a.pointer); } if (instanced) { if (!instance_buffer_) instance_buffer_ = buffer{}; instance_buffer_.bind(); for (auto const & a : attribs.attribs) { if (a.divisor == 0) continue; gl::EnableVertexAttribArray(a.index); gl::VertexAttribDivisor(a.index, a.divisor); if (a.integer) gl::VertexAttribIPointer(a.index, a.size, a.type, attribs.instance_size, a.pointer); else gl::VertexAttribPointer(a.index, a.size, a.type, a.normalized, attribs.instance_size, a.pointer); } } } mesh mesh::create(imported_mesh const & m) { mesh result; result.setup(m.attribs); result.load_raw(m); return result; } void mesh::load_raw(void const * vertices, std::size_t vertex_size, std::size_t count, GLenum primitive_type, GLenum usage) { assert(info_.stride_ == vertex_size); if (auto n = detail::get_primitive_type_vertex_count(primitive_type); n) assert((count % (*n)) == 0); assert(vertex_buffer_); vertex_buffer_.load(vertices, vertex_size * count, usage); info_.vertex_count_ = count; info_.index_count_ = 0; info_.indexed_ = false; info_.primitive_type_ = primitive_type; } static std::size_t index_size(GLenum type) { switch (type) { case gl::UNSIGNED_BYTE: return 1; case gl::UNSIGNED_SHORT: return 2; case gl::UNSIGNED_INT: return 4; default: throw util::exception("Unknown undex type"); } } void mesh::load_raw(void const * vertices, std::size_t vertex_size, std::size_t vertex_count, void const * indices, GLenum index_type, std::size_t index_count, GLenum primitive_type, GLenum usage) { assert(info_.stride_ == vertex_size); if (auto n = detail::get_primitive_type_vertex_count(primitive_type); n) assert((index_count % (*n)) == 0); assert(vertex_buffer_); if (!index_buffer_) index_buffer_ = {}; array_.bind(); vertex_buffer_.load(vertices, vertex_size * vertex_count, usage); index_buffer_.load(indices, index_size(index_type) * index_count, usage); info_.vertex_count_ = vertex_count; info_.index_count_ = index_count; info_.indexed_ = true; info_.primitive_type_ = primitive_type; info_.index_type_ = index_type; } void mesh::load_raw(imported_mesh const & m) { setup(m.attribs); load_raw(m.vertices.data(), m.attribs.vertex_size, m.vertices.size() / m.attribs.vertex_size, m.indices.data(), gl::UNSIGNED_INT, m.indices.size(), gl::TRIANGLES, gl::STATIC_DRAW); } void mesh::draw() const { draw(0, is_indexed() ? index_count() : vertex_count(), instance_count()); } void mesh::draw(std::size_t first, std::size_t count, std::size_t instance_count) const { if (is_indexed()) { assert(first + count <= index_count()); if (is_instanced()) { assert(instance_count <= this->instance_count()); if (count != 0 && instance_count != 0) { bind(); gl::DrawElementsInstanced(primitive_type(), count, index_type(), reinterpret_cast(detail::index_size(index_type()) * first), instance_count); } } else { if (count != 0) { bind(); gl::DrawElements(primitive_type(), count, index_type(), reinterpret_cast(detail::index_size(index_type()) * first)); } } } else { assert(first + count <= vertex_count()); if (is_instanced()) { assert(instance_count <= this->instance_count()); if (count != 0 && instance_count != 0) { bind(); gl::DrawArraysInstanced(primitive_type(), first, count, instance_count); } } else { if (count != 0) { bind(); gl::DrawArrays(primitive_type(), first, count); } } } } imported_mesh load_mesh(std::string_view data) { // These should be in sync with convert-mesh.py static std::uint32_t const SECTION_MESH = 1; static std::uint32_t const SECTION_BONES = 2; static std::uint32_t const SECTION_POSE = 3; static std::uint32_t const POSITION_MASK = 1 << 0; static std::uint32_t const NORMALS_MASK = 1 << 1; static std::uint32_t const COLORS_MASK = 1 << 2; static std::uint32_t const TEXCOORDS_MASK = 1 << 3; static std::uint32_t const WEIGHTS_MASK = 1 << 4; std::uint32_t vertex_format = 0; util::binary_istream s{data}; imported_mesh result; auto parse_section_mesh = [&] { vertex_format = s.read(); if (vertex_format & POSITION_MASK) result.attribs += make_attribs_description>(); else result.attribs += make_attribs_description(); if (vertex_format & NORMALS_MASK) result.attribs += make_attribs_description>(); else result.attribs += make_attribs_description(); if (vertex_format & COLORS_MASK) result.attribs += make_attribs_description>>(); else result.attribs += make_attribs_description(); if (vertex_format & TEXCOORDS_MASK) result.attribs += make_attribs_description>(); else result.attribs += make_attribs_description(); if (vertex_format & WEIGHTS_MASK) result.attribs += make_attribs_description>, gfx::normalized>>(); else result.attribs += make_attribs_description(); auto vertex_count = s.read(); auto vertex_ptr = s.read_raw(vertex_count * result.attribs.vertex_size); auto index_count = s.read(); auto index_ptr = s.read_ptr(index_count); result.vertices = {vertex_ptr, vertex_ptr + vertex_count * result.attribs.vertex_size}; result.indices = {index_ptr, index_ptr + index_count}; }; auto parse_section_bones = [&] { auto bone_count = s.read(); auto bone_ptr = s.read_ptr(bone_count); result.bones = {bone_ptr, bone_ptr + bone_count}; }; auto parse_section_pose = [&] { auto name_length = s.read(); auto name_ptr = s.read_raw(name_length); auto pose_count = s.read(); if (pose_count != result.bones.size()) throw util::exception("Number of transforms in a pose must be equal to the number of bones"); auto pose_ptr = s.read_ptr>(pose_count); std::string_view name(name_ptr, name_length); result.poses[name] = {pose_ptr, pose_ptr + pose_count}; }; bool had_section_mesh = false; bool had_section_bones = false; while (!s.eof()) { auto section_type = s.read(); switch (section_type) { case SECTION_MESH: if (had_section_mesh) throw util::exception("Section 'mesh' must not repeat"); parse_section_mesh(); had_section_mesh = true; break; case SECTION_BONES: if (had_section_bones) throw util::exception("Section 'bones' must not repeat"); if (!had_section_mesh) throw util::exception("Section 'bones' must come after section 'mesh'"); if ((vertex_format & WEIGHTS_MASK) == 0) throw util::exception("Section 'bones' requires weights in vertex format"); parse_section_bones(); had_section_bones = true; break; case SECTION_POSE: if (!had_section_bones) throw util::exception("Section 'pose' must come after section 'bones'"); parse_section_pose(); break; default: throw util::exception("Unknown section code " + util::to_string(section_type)); } } if (!had_section_mesh) throw util::exception("Section 'mesh' must be present"); return result; } }