libdonut 2.3.6
Application framework for cross-platform game development in C++20
Loading...
Searching...
No Matches
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>
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
18namespace donut::graphics {
19
25template <typename T>
26concept 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
44enum 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
60enum 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
73enum class MeshIndexType : std::uint32_t {
74 U8 = 0x1401,
75 U16 = 0x1403,
76 U32 = 0x1405,
77};
78
79namespace detail {
80
81class MeshStatePreserver {
82public:
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
91private:
92 std::int32_t vertexArrayBinding = 0;
93 std::int32_t arrayBufferBinding = 0;
94};
95
96void bindVertexArray(Handle handle);
97void bindArrayBuffer(Handle handle);
98void bindElementArrayBuffer(Handle handle);
99void enableVertexAttribArray(std::uint32_t index);
100void vertexAttribDivisor(std::uint32_t index, std::uint32_t divisor);
101void vertexAttribPointerUint(std::uint32_t index, std::size_t count, std::size_t stride, std::uintptr_t offset);
102void vertexAttribPointerFloat(std::uint32_t index, std::size_t count, std::size_t stride, std::uintptr_t offset);
103void bufferArrayBufferData(std::size_t size, const void* data, MeshBufferUsage usage);
104void bufferElementArrayBufferData(std::size_t size, const void* data, MeshBufferUsage usage);
105
106template <bool IsInstance>
107inline void enableVertexAttribute(std::uint32_t index) {
108 enableVertexAttribArray(index);
109 if constexpr (IsInstance) {
110 vertexAttribDivisor(index, 1);
111 }
112}
113
114template <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
158template <typename Tuple>
159struct is_vertex_attributes : std::false_type {};
160
161template <typename... Ts>
162struct is_vertex_attributes<std::tuple<Ts...>> : std::bool_constant<(vertex_attribute<std::remove_cvref_t<Ts>> && ...)> {};
163
164template <typename Tuple>
165inline constexpr bool is_vertex_attributes_v = is_vertex_attributes<Tuple>::value;
166
167} // namespace detail
168
174template <typename T>
175concept 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
183struct NoIndex {};
184
190template <typename T>
191concept 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
200struct NoInstance {};
201
207template <typename T>
208concept 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
225template <typename Vertex, typename Index = NoIndex, typename Instance = NoInstance>
226class Mesh {
227public:
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
401private:
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 instancesUsage, std::span< const Vertex > vertices, std::span< const Instance > instances)
Constructor for meshes that have a vertex buffer and an instance buffer.
Definition Mesh.hpp:278
Handle getInstanceBuffer() const noexcept
Get an opaque handle to the GPU representation of the instance buffer.
Definition Mesh.hpp:383
Mesh(MeshBufferUsage verticesUsage, std::span< const Vertex > vertices)
Constructor for meshes that only have a vertex buffer.
Definition Mesh.hpp:245
Handle getIndexBuffer() const noexcept
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
Mesh(MeshBufferUsage verticesUsage, MeshBufferUsage indicesUsage, MeshBufferUsage instancesUsage, std::span< const Vertex > vertices, std::span< const Index > indices, std::span< const Instance > instances)
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
Set the contents of the vertex and index buffers.
Definition Mesh.hpp:336
void setVertices(MeshBufferUsage verticesUsage, std::span< const Vertex > vertices) noexcept
Set the contents of the vertex buffer.
Definition Mesh.hpp:317
Mesh(MeshBufferUsage verticesUsage, MeshBufferUsage indicesUsage, std::span< const Vertex > vertices, std::span< const Index > indices)
Constructor for meshes that have a vertex buffer and an index buffer.
Definition Mesh.hpp:261
Concept that checks if a type is a valid index type.
Definition Mesh.hpp:191
Concept that checks if a type is a valid instance type.
Definition Mesh.hpp:208
Concept that checks if a type is a valid vertex type.
Definition Mesh.hpp:175
Concept that checks if a type is a valid vertex attribute.
Definition Mesh.hpp:26
Definition Buffer.hpp:7
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
@ U8
Each pixel component is an 8-bit unsigned integer.
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.
MeshIndexType
Specification of which type of indices is used in the index buffer of a particular Mesh.
Definition Mesh.hpp:73
@ 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