libdonut  2.3.2
Application framework for cross-platform game development in C++20
Mesh.hpp
Go to the documentation of this file.
1 #ifndef DONUT_GRAPHICS_MESH_HPP
2 #define DONUT_GRAPHICS_MESH_HPP
3 
7 #include <donut/math.hpp>
8 #include <donut/reflection.hpp>
9 
10 #include <cstddef> // std::size_t, std::byte
11 #include <cstdint> // std::int32_t, std::uint32_t, std::uintptr_t
12 #include <memory> // std::addressof
13 #include <span> // std::span
14 #include <stdexcept> // std::invalid_argument
15 #include <type_traits> // std::is_same_v, std::remove_cvref_t, std::false_type, std::true_type, std::bool_constant, std::is_aggregate_v, std::is_standard_layout_v, std::conditional_t
16 #include <utility> // std::declval
17 
18 namespace donut::graphics {
19 
25 template <typename T>
26 concept vertex_attribute = //
27  std::is_same_v<T, u32> || //
28  std::is_same_v<T, float> || //
29  std::is_same_v<T, vec2> || //
30  std::is_same_v<T, vec3> || //
31  std::is_same_v<T, vec4> || //
32  std::is_same_v<T, mat2> || //
33  std::is_same_v<T, mat3> || //
34  std::is_same_v<T, mat4>;
35 
44 enum class MeshBufferUsage : std::uint32_t {
45  STATIC_COPY = 0x88E6,
46  STATIC_DRAW = 0x88E4,
47  STATIC_READ = 0x88E5,
48  DYNAMIC_COPY = 0x88EA,
49  DYNAMIC_DRAW = 0x88E8,
50  DYNAMIC_READ = 0x88E9,
51  STREAM_COPY = 0x88E2,
52  STREAM_DRAW = 0x88E0,
53  STREAM_READ = 0x88E1,
54 };
55 
60 enum class MeshPrimitiveType : std::uint32_t {
61  POINTS = 0x0000,
62  LINES = 0x0001,
63  LINE_LOOP = 0x0002,
64  LINE_STRIP = 0x0003,
65  TRIANGLES = 0x0004,
66  TRIANGLE_STRIP = 0x0005,
67 };
68 
73 enum class MeshIndexType : std::uint32_t {
74  U8 = 0x1401,
75  U16 = 0x1403,
76  U32 = 0x1405,
77 };
78 
79 namespace detail {
80 
81 class MeshStatePreserver {
82 public:
83  [[nodiscard]] MeshStatePreserver() noexcept;
84  ~MeshStatePreserver();
85 
86  MeshStatePreserver(const MeshStatePreserver&) = delete;
87  MeshStatePreserver(MeshStatePreserver&&) = delete;
88  MeshStatePreserver& operator=(const MeshStatePreserver&) = delete;
89  MeshStatePreserver& operator=(MeshStatePreserver&&) = delete;
90 
91 private:
92  std::int32_t vertexArrayBinding = 0;
93  std::int32_t arrayBufferBinding = 0;
94 };
95 
96 void bindVertexArray(Handle handle);
97 void bindArrayBuffer(Handle handle);
98 void bindElementArrayBuffer(Handle handle);
99 void enableVertexAttribArray(std::uint32_t index);
100 void vertexAttribDivisor(std::uint32_t index, std::uint32_t divisor);
101 void vertexAttribPointerUint(std::uint32_t index, std::size_t count, std::size_t stride, std::uintptr_t offset);
102 void vertexAttribPointerFloat(std::uint32_t index, std::size_t count, std::size_t stride, std::uintptr_t offset);
103 void bufferArrayBufferData(std::size_t size, const void* data, MeshBufferUsage usage);
104 void bufferElementArrayBufferData(std::size_t size, const void* data, MeshBufferUsage usage);
105 
106 template <bool IsInstance>
107 inline void enableVertexAttribute(std::uint32_t index) {
108  enableVertexAttribArray(index);
109  if constexpr (IsInstance) {
110  vertexAttribDivisor(index, 1);
111  }
112 }
113 
114 template <bool IsInstance, typename T>
115 [[nodiscard]] inline std::uint32_t setupVertexAttribute(std::uint32_t index, std::size_t stride, std::uintptr_t offset) {
116  if constexpr (std::is_same_v<T, std::uint32_t>) {
117  enableVertexAttribute<IsInstance>(index);
118  vertexAttribPointerUint(index++, 1, stride, offset);
119  } else if constexpr (std::is_same_v<T, float>) {
120  enableVertexAttribute<IsInstance>(index);
121  vertexAttribPointerFloat(index++, 1, stride, offset);
122  } else if constexpr (std::is_same_v<T, vec2>) {
123  enableVertexAttribute<IsInstance>(index);
124  vertexAttribPointerFloat(index++, 2, stride, offset);
125  } else if constexpr (std::is_same_v<T, vec3>) {
126  enableVertexAttribute<IsInstance>(index);
127  vertexAttribPointerFloat(index++, 3, stride, offset);
128  } else if constexpr (std::is_same_v<T, vec4>) {
129  enableVertexAttribute<IsInstance>(index);
130  vertexAttribPointerFloat(index++, 4, stride, offset);
131  } else if constexpr (std::is_same_v<T, mat2>) {
132  enableVertexAttribute<IsInstance>(index);
133  vertexAttribPointerFloat(index++, 2, stride, offset);
134  enableVertexAttribute<IsInstance>(index);
135  vertexAttribPointerFloat(index++, 2, stride, offset + sizeof(float) * 2);
136  } else if constexpr (std::is_same_v<T, mat3>) {
137  enableVertexAttribute<IsInstance>(index);
138  vertexAttribPointerFloat(index++, 3, stride, offset);
139  enableVertexAttribute<IsInstance>(index);
140  vertexAttribPointerFloat(index++, 3, stride, offset + sizeof(float) * 3);
141  enableVertexAttribute<IsInstance>(index);
142  vertexAttribPointerFloat(index++, 3, stride, offset + sizeof(float) * 6);
143  } else if constexpr (std::is_same_v<T, mat4>) {
144  enableVertexAttribute<IsInstance>(index);
145  vertexAttribPointerFloat(index++, 4, stride, offset);
146  enableVertexAttribute<IsInstance>(index);
147  vertexAttribPointerFloat(index++, 4, stride, offset + sizeof(float) * 4);
148  enableVertexAttribute<IsInstance>(index);
149  vertexAttribPointerFloat(index++, 4, stride, offset + sizeof(float) * 8);
150  enableVertexAttribute<IsInstance>(index);
151  vertexAttribPointerFloat(index++, 4, stride, offset + sizeof(float) * 12);
152  } else {
153  throw std::invalid_argument{"Invalid vertex attribute type!"};
154  }
155  return index;
156 }
157 
158 template <typename Tuple>
159 struct is_vertex_attributes : std::false_type {};
160 
161 template <typename... Ts>
162 struct is_vertex_attributes<std::tuple<Ts...>> : std::bool_constant<(vertex_attribute<std::remove_cvref_t<Ts>> && ...)> {};
163 
164 template <typename Tuple>
165 inline constexpr bool is_vertex_attributes_v = is_vertex_attributes<Tuple>::value;
166 
167 } // namespace detail
168 
174 template <typename T>
175 concept mesh_vertex = //
176  std::is_aggregate_v<T> && // Vertex must be an aggregate type, such as a plain struct.
177  std::is_standard_layout_v<T> && // Vertex must have standard layout in order to be compatible with the layout expected by the shader.
178  detail::is_vertex_attributes_v<decltype(reflection::fields(std::declval<T>()))>; // All vertex fields must be valid vertex attributes.
179 
183 struct NoIndex {};
184 
190 template <typename T>
191 concept mesh_index = //
192  std::is_same_v<T, NoIndex> || //
193  std::is_same_v<T, u8> || //
194  std::is_same_v<T, u16> || //
195  std::is_same_v<T, u32>;
196 
200 struct NoInstance {};
201 
207 template <typename T>
208 concept mesh_instance = //
209  std::is_aggregate_v<T> && // Instance must be an aggregate type, such as a plain struct.
210  std::is_standard_layout_v<T> && // Instance must have standard layout in order to be compatible with the layout expected by the shader.
211  detail::is_vertex_attributes_v<decltype(reflection::fields(std::declval<T>()))>; // All instance fields must be valid vertex attributes.
212 
225 template <typename Vertex, typename Index = NoIndex, typename Instance = NoInstance>
226 class Mesh {
227 public:
228  static_assert(mesh_vertex<Vertex>, "Mesh template parameter \"Vertex\" must be a valid vertex type.");
229  static_assert(mesh_index<Index>, "Mesh template parameter \"Index\" must be a valid index type.");
230  static_assert(mesh_instance<Instance>, "Mesh template parameter \"Instance\" must be a valid instance type.");
231 
233  static constexpr bool IS_INDEXED = !std::is_same_v<Index, NoIndex>;
234 
236  static constexpr bool IS_INSTANCED = !std::is_same_v<Instance, NoInstance>;
237 
245  Mesh(MeshBufferUsage verticesUsage, std::span<const Vertex> vertices) requires(!IS_INDEXED && !IS_INSTANCED) {
246  const detail::MeshStatePreserver preserver{};
247  detail::bindVertexArray(vao.get());
248  bufferVertexData(verticesUsage, vertices, 0);
249  }
250 
261  Mesh(MeshBufferUsage verticesUsage, MeshBufferUsage indicesUsage, std::span<const Vertex> vertices, std::span<const Index> indices) requires(IS_INDEXED && !IS_INSTANCED) {
262  const detail::MeshStatePreserver preserver{};
263  detail::bindVertexArray(vao.get());
264  bufferVertexData(verticesUsage, vertices, 0);
265  bufferIndexData(indicesUsage, indices);
266  }
267 
278  Mesh(MeshBufferUsage verticesUsage, MeshBufferUsage instancesUsage, std::span<const Vertex> vertices, std::span<const Instance> instances) requires(!IS_INDEXED && IS_INSTANCED)
279  {
280  const detail::MeshStatePreserver preserver{};
281  detail::bindVertexArray(vao.get());
282  bufferVertexData(verticesUsage, vertices, 0);
283  bufferInstanceData(instancesUsage, instances, static_cast<std::uint32_t>(reflection::aggregate_size_v<Vertex>));
284  }
285 
299  Mesh(MeshBufferUsage verticesUsage, MeshBufferUsage indicesUsage, MeshBufferUsage instancesUsage, std::span<const Vertex> vertices, std::span<const Index> indices,
300  std::span<const Instance> instances) requires(IS_INDEXED && IS_INSTANCED) {
301  const detail::MeshStatePreserver preserver{};
302  detail::bindVertexArray(vao.get());
303  bufferVertexData(verticesUsage, vertices, 0);
304  bufferIndexData(indicesUsage, indices);
305  bufferInstanceData(instancesUsage, instances, static_cast<std::uint32_t>(reflection::aggregate_size_v<Vertex>));
306  }
307 
317  void setVertices(MeshBufferUsage verticesUsage, std::span<const Vertex> vertices) noexcept requires(!IS_INDEXED) {
318  const detail::MeshStatePreserver preserver{};
319  detail::bindVertexArray(vao.get());
320  detail::bindArrayBuffer(vbo.get());
321  detail::bufferArrayBufferData(sizeof(Vertex) * vertices.size(), vertices.data(), verticesUsage);
322  }
323 
336  void setVertices(MeshBufferUsage verticesUsage, MeshBufferUsage indicesUsage, std::span<const Vertex> vertices, std::span<const Index> indices) noexcept requires(IS_INDEXED) {
337  const detail::MeshStatePreserver preserver{};
338  detail::bindVertexArray(vao.get());
339  detail::bindArrayBuffer(vbo.get());
340  detail::bufferArrayBufferData(sizeof(Vertex) * vertices.size(), vertices.data(), verticesUsage);
341  detail::bindElementArrayBuffer(ebo.get());
342  detail::bufferElementArrayBufferData(sizeof(Index) * indices.size(), indices.data(), indicesUsage);
343  }
344 
355  [[nodiscard]] Handle getVertexBuffer() const noexcept {
356  return vbo.get();
357  }
358 
369  [[nodiscard]] Handle getIndexBuffer() const noexcept requires(IS_INDEXED) {
370  return ebo.get();
371  }
372 
383  [[nodiscard]] Handle getInstanceBuffer() const noexcept requires(IS_INSTANCED) {
384  return ibo.get();
385  }
386 
397  [[nodiscard]] Handle get() const noexcept {
398  return vao.get();
399  }
400 
401 private:
402  void bufferVertexData(MeshBufferUsage usage, std::span<const Vertex> vertices, std::uint32_t attributeOffset) {
403  static_assert(std::is_aggregate_v<Vertex>, "Vertex type must be an aggregate type!");
404  static_assert(std::is_standard_layout_v<Vertex>, "Vertex type must have standard layout!");
405  detail::bindArrayBuffer(vbo.get());
406  detail::bufferArrayBufferData(sizeof(Vertex) * vertices.size(), vertices.data(), usage);
407  Vertex dummyVertex{};
408  reflection::forEach(reflection::fields(dummyVertex), [&dummyVertex, &attributeOffset]<typename T>(T& dummyField) {
409  const std::byte* const basePointer = reinterpret_cast<const std::byte*>(std::addressof(dummyVertex));
410  const std::byte* const attributePointer = reinterpret_cast<const std::byte*>(std::addressof(dummyField));
411  const std::uintptr_t offset = static_cast<std::uintptr_t>(attributePointer - basePointer);
412  attributeOffset = detail::setupVertexAttribute<false, T>(attributeOffset, sizeof(Vertex), offset);
413  });
414  }
415 
416  void bufferIndexData(MeshBufferUsage usage, std::span<const Index> indices) requires(IS_INDEXED) {
417  detail::bindElementArrayBuffer(ebo.get());
418  detail::bufferElementArrayBufferData(sizeof(Index) * indices.size(), indices.data(), usage);
419  }
420 
421  void bufferInstanceData(MeshBufferUsage usage, std::span<const Instance> instances, std::uint32_t attributeOffset) requires(IS_INSTANCED) {
422  static_assert(std::is_aggregate_v<Instance>, "Instance type must be an aggregate type!");
423  static_assert(std::is_standard_layout_v<Instance>, "Instance type must have standard layout!");
424  detail::bindArrayBuffer(ibo.get());
425  detail::bufferArrayBufferData(sizeof(Instance) * instances.size(), instances.data(), usage);
426  Instance dummyInstance{};
427  reflection::forEach(reflection::fields(dummyInstance), [&dummyInstance, &attributeOffset]<typename T>(T& dummyField) {
428  const std::byte* const basePointer = reinterpret_cast<const std::byte*>(std::addressof(dummyInstance));
429  const std::byte* const attributePointer = reinterpret_cast<const std::byte*>(std::addressof(dummyField));
430  const std::uintptr_t offset = static_cast<std::uintptr_t>(attributePointer - basePointer);
431  attributeOffset = detail::setupVertexAttribute<true, T>(attributeOffset, sizeof(Instance), offset);
432  });
433  }
434 
435  VertexArray vao{};
436  Buffer vbo{};
437  [[no_unique_address]] std::conditional_t<IS_INDEXED, Buffer, NoIndex> ebo{};
438  [[no_unique_address]] std::conditional_t<IS_INSTANCED, Buffer, NoInstance> ibo{};
439 };
440 
441 } // namespace donut::graphics
442 
443 #endif
Generic abstraction of a GPU vertex array object and its associated buffers.
Definition: Mesh.hpp:226
Mesh(MeshBufferUsage verticesUsage, MeshBufferUsage indicesUsage, std::span< const Vertex > vertices, std::span< const Index > indices) requires(IS_INDEXED &&!IS_INSTANCED)
Constructor for meshes that have a vertex buffer and an index buffer.
Definition: Mesh.hpp:261
Mesh(MeshBufferUsage verticesUsage, MeshBufferUsage instancesUsage, std::span< const Vertex > vertices, std::span< const Instance > instances) requires(!IS_INDEXED &&IS_INSTANCED)
Constructor for meshes that have a vertex buffer and an instance buffer.
Definition: Mesh.hpp:278
Handle getIndexBuffer() const noexcept requires(IS_INDEXED)
Get an opaque handle to the GPU representation of the index buffer.
Definition: Mesh.hpp:369
Handle get() const noexcept
Get an opaque handle to the GPU representation of the vertex array.
Definition: Mesh.hpp:397
Handle getVertexBuffer() const noexcept
Get an opaque handle to the GPU representation of the vertex buffer.
Definition: Mesh.hpp:355
void setVertices(MeshBufferUsage verticesUsage, std::span< const Vertex > vertices) noexcept requires(!IS_INDEXED)
Set the contents of the vertex buffer.
Definition: Mesh.hpp:317
Handle getInstanceBuffer() const noexcept requires(IS_INSTANCED)
Get an opaque handle to the GPU representation of the instance buffer.
Definition: Mesh.hpp:383
Mesh(MeshBufferUsage verticesUsage, MeshBufferUsage indicesUsage, MeshBufferUsage instancesUsage, std::span< const Vertex > vertices, std::span< const Index > indices, std::span< const Instance > instances) requires(IS_INDEXED &&IS_INSTANCED)
Constructor for meshes that have a vertex buffer, an index buffer and an instance buffer.
Definition: Mesh.hpp:299
void setVertices(MeshBufferUsage verticesUsage, MeshBufferUsage indicesUsage, std::span< const Vertex > vertices, std::span< const Index > indices) noexcept requires(IS_INDEXED)
Set the contents of the vertex and index buffers.
Definition: Mesh.hpp:336
Mesh(MeshBufferUsage verticesUsage, std::span< const Vertex > vertices) requires(!IS_INDEXED &&!IS_INSTANCED)
Constructor for meshes that only have a vertex buffer.
Definition: Mesh.hpp:245
Definition: Buffer.hpp:7
concept vertex_attribute
Concept that checks if a type is a valid vertex attribute.
Definition: Mesh.hpp:26
concept mesh_vertex
Concept that checks if a type is a valid vertex type.
Definition: Mesh.hpp:175
std::uint32_t Handle
Generic GPU resource handle.
Definition: Handle.hpp:11
MeshBufferUsage
Hint to the graphics driver implementation regarding the intended access pattern of a particular GPU ...
Definition: Mesh.hpp:44
MeshPrimitiveType
Specification of which kind of graphical primitive is defined by an associated sequence of vertices i...
Definition: Mesh.hpp:60
@ TRIANGLE_STRIP
Each point, except the first two, forms a filled triangle with the previous two points.
@ LINE_LOOP
Each point forms a line segment to the previous point, where the last point connects back to the firs...
@ TRIANGLES
Each consecutive triple of points forms an individual filled triangle.
@ LINES
Each consecutive pair of points forms an individual line segment.
@ LINE_STRIP
Each point, except the first, forms a line segment to the previous point.
concept mesh_instance
Concept that checks if a type is a valid instance type.
Definition: Mesh.hpp:208
concept mesh_index
Concept that checks if a type is a valid index type.
Definition: Mesh.hpp:191
MeshIndexType
Specification of which type of indices is used in the index buffer of a particular Mesh.
Definition: Mesh.hpp:73
@ U8
Unsigned 8-bit integer.
@ U32
Unsigned 32-bit integer.
@ U16
Unsigned 16-bit integer.
constexpr auto fields(auto &&aggregate) noexcept
Get a tuple of references to each of the fields of an aggregate.
Definition: reflection.hpp:86
constexpr void forEach(auto &&tuple, auto fn)
Execute a function once for each element in a given tuple, sequentially.
Definition: reflection.hpp:207
Tag type for specifying that a Mesh does not have an index buffer.
Definition: Mesh.hpp:183
Tag type for specifying that a Mesh does not have an instance buffer.
Definition: Mesh.hpp:200