载入中...
搜索中...
未找到
GraphicsMesh.cpp
浏览该文件的文档.
1// Vulkan backend implementation — mesh creation and drawing.
2//
3// Re-split from the merged dev single-TU Graphics.cpp (pure move;
4// dev changes preserved). Shared helpers live in GraphicsInternal.h.
5
7#include "graphics/Light.h"
11
12#include <SDL2/SDL.h>
13#include <SDL2/SDL_vulkan.h>
14
15#include <algorithm>
16#include <array>
17#include <cmath>
18#include <cstdio>
19#include <cstdlib>
20#include <cstring>
21#include <functional>
22#include <stdexcept>
23#include <string>
24#include <vector>
25#if !defined(_WIN32)
26#include <unistd.h>
27#endif
28
29#include "common/Exception.h"
31#include "common/config.h"
33#include "image/Image.h"
34#include "image/ImageData.h"
35#include "zeroerr/assert.h"
36
37#include <memory>
38
39
40#include <assimp/mesh.h>
41#include <assimp/matrix3x3.h>
42#include <assimp/matrix4x4.h>
43#include <assimp/vector3.h>
44#include <glm/gtc/matrix_transform.hpp>
45
47
48namespace eve::graphics::vulkan {
49
50// --- Mesh creation and drawing ------------------------------------------------
51
53 ASSERT(initialized);
54 if (!initialized) throw Exception("newMeshFromAssimp: graphics not initialized");
55 if (mesh.mNumVertices == 0 || mesh.mNumFaces == 0)
56 throw Exception("newMeshFromAssimp: empty mesh");
57
58 std::vector<MeshVertex> verts;
59 verts.reserve(mesh.mNumVertices);
60 std::vector<float> basePos;
61 std::vector<float> baseNrm;
62 std::vector<float> baseUv;
63 if (mesh.mNumAnimMeshes > 0) {
64 basePos.reserve(mesh.mNumVertices * 3);
65 baseNrm.reserve(mesh.mNumVertices * 3);
66 baseUv.reserve(mesh.mNumVertices * 2);
67 }
68 for (unsigned i = 0; i < mesh.mNumVertices; ++i) {
69 MeshVertex v{};
70 v.pos = {mesh.mVertices[i].x, mesh.mVertices[i].y, mesh.mVertices[i].z};
71 if (mesh.HasNormals())
72 v.normal = {mesh.mNormals[i].x, mesh.mNormals[i].y, mesh.mNormals[i].z};
73 else
74 v.normal = {0.f, 1.f, 0.f};
75 if (mesh.HasTangentsAndBitangents()) {
76 const auto &tangent = mesh.mTangents[i];
77 const auto &bitangent = mesh.mBitangents[i];
78 const glm::vec3 t(tangent.x, tangent.y, tangent.z);
79 const glm::vec3 b(bitangent.x, bitangent.y, bitangent.z);
80 v.tangent = glm::vec4(t, glm::dot(glm::cross(v.normal, t), b) < 0.f ? -1.f : 1.f);
81 }
82 if (mesh.HasTextureCoords(0))
83 v.uv = {mesh.mTextureCoords[0][i].x, mesh.mTextureCoords[0][i].y};
84 else
85 v.uv = {0.f, 0.f};
86 verts.push_back(v);
87 if (mesh.mNumAnimMeshes > 0) {
88 basePos.push_back(v.pos.x);
89 basePos.push_back(v.pos.y);
90 basePos.push_back(v.pos.z);
91 baseNrm.push_back(v.normal.x);
92 baseNrm.push_back(v.normal.y);
93 baseNrm.push_back(v.normal.z);
94 baseUv.push_back(v.uv.x);
95 baseUv.push_back(v.uv.y);
96 }
97 }
98
99 std::vector<uint32_t> indices;
100 indices.reserve(mesh.mNumFaces * 3);
101 for (unsigned f = 0; f < mesh.mNumFaces; ++f) {
102 const aiFace &face = mesh.mFaces[f];
103 if (face.mNumIndices != 3) continue;
104 indices.push_back(face.mIndices[0]);
105 indices.push_back(face.mIndices[1]);
106 indices.push_back(face.mIndices[2]);
107 }
108 if (indices.empty()) throw Exception("newMeshFromAssimp: no triangle faces");
109
110 std::unique_ptr<GpuMesh> gpu;
111 if (mesh.mNumVertices <= 65535u) {
112 std::vector<uint16_t> idx16;
113 idx16.reserve(indices.size());
114 for (uint32_t i : indices) idx16.push_back(uint16_t(i));
115 gpu = uploadGpuMesh16(device, frameToken(), verts, idx16);
116 } else {
117 gpu = uploadGpuMesh(device, frameToken(), verts, indices);
118 }
119 auto handle = makeMeshHandle(*gpu);
120 handle->captureImportedAttributes(mesh);
121 // Retain the CPU morph base pose only when the mesh actually has morphs;
122 // otherwise the base pos/nrm/uv copies would linger at ~32B/vertex for no reason.
123 if (mesh.mNumAnimMeshes > 0) {
124 handle->initMorphBase(int(mesh.mNumVertices), basePos.data(), baseNrm.data(), baseUv.data());
125 }
126 // Assimp morph targets (VRM / glTF blend shapes often land here).
127 for (unsigned m = 0; m < mesh.mNumAnimMeshes; ++m) {
128 const aiAnimMesh *am = mesh.mAnimMeshes[m];
129 if (!am || !am->mVertices || am->mNumVertices != mesh.mNumVertices) continue;
130 std::string morphName =
131 am->mName.length ? am->mName.C_Str() : ("morph" + std::to_string(m));
132 std::vector<float> absPos(size_t(am->mNumVertices) * 3u);
133 for (unsigned i = 0; i < am->mNumVertices; ++i) {
134 absPos[size_t(i) * 3u + 0] = am->mVertices[i].x;
135 absPos[size_t(i) * 3u + 1] = am->mVertices[i].y;
136 absPos[size_t(i) * 3u + 2] = am->mVertices[i].z;
137 }
138 handle->addMorphTargetAbsolute(morphName, absPos.data());
139 }
140 handle->markMorphClean();
141 Mesh *raw = handle.get();
142 assignMeshBounds(raw, verts);
143 registerMeshRecord(gpu.get(), &verts, &indices);
144 ownedGpuMeshes.push_back(std::move(gpu));
145 ownedMeshes.push_back(std::move(handle));
146 return raw;
147}
148
149Mesh *Graphics::newMeshFromAssimp(const ::aiMesh &mesh, const aiMatrix4x4 &worldTransform) {
150 ASSERT(initialized);
151 if (!initialized) throw Exception("newMeshFromAssimp: graphics not initialized");
152 if (mesh.mNumVertices == 0 || mesh.mNumFaces == 0)
153 throw Exception("newMeshFromAssimp: empty mesh");
154
155 std::vector<aiVector3D> positions(mesh.mNumVertices);
156 std::vector<aiVector3D> normals(mesh.mNumVertices);
157 std::vector<aiVector3D> tangents;
158 std::vector<aiVector3D> bitangents;
159 if (mesh.HasTangentsAndBitangents()) {
160 tangents.resize(mesh.mNumVertices);
161 bitangents.resize(mesh.mNumVertices);
162 const aiMatrix3x3 linear(worldTransform);
163 for (unsigned i = 0; i < mesh.mNumVertices; ++i) {
164 tangents[i] = linear * mesh.mTangents[i];
165 bitangents[i] = linear * mesh.mBitangents[i];
166 tangents[i].Normalize();
167 bitangents[i].Normalize();
168 }
169 }
170 aiMatrix3x3 nmat(worldTransform);
171 const float ndet = nmat.Determinant();
172 if (std::fabs(ndet) > 1e-8f) {
173 nmat.Inverse();
174 nmat.Transpose();
175 }
176 for (unsigned i = 0; i < mesh.mNumVertices; ++i) {
177 positions[i] = worldTransform * mesh.mVertices[i];
178 if (mesh.HasNormals()) {
179 normals[i] = nmat * mesh.mNormals[i];
180 normals[i].Normalize();
181 } else {
182 normals[i] = aiVector3D(0.f, 1.f, 0.f);
183 }
184 }
185
186 // Negative determinant mirrors the mesh: winding flips while inverse-transpose
187 // keeps normals consistent, so back-face cull would hide the visible side.
188 const bool flipWinding = worldTransform.Determinant() < 0.f;
189 std::vector<aiFace> flippedFaces;
190 std::vector<unsigned> flippedIdx;
191 if (flipWinding) {
192 flippedFaces.resize(mesh.mNumFaces);
193 flippedIdx.resize(size_t(mesh.mNumFaces) * 3u);
194 for (unsigned f = 0; f < mesh.mNumFaces; ++f) {
195 const aiFace &src = mesh.mFaces[f];
196 aiFace &dst = flippedFaces[f];
197 dst.mNumIndices = src.mNumIndices;
198 if (src.mNumIndices == 3 && src.mIndices) {
199 unsigned *idx = flippedIdx.data() + size_t(f) * 3u;
200 idx[0] = src.mIndices[0];
201 idx[1] = src.mIndices[2];
202 idx[2] = src.mIndices[1];
203 dst.mIndices = idx;
204 } else {
205 dst.mIndices = src.mIndices;
206 }
207 }
208 }
209
210 // Non-owning view — do not let aiMesh destructor free borrowed pointers.
211 aiMesh tmp{};
212 tmp.mPrimitiveTypes = mesh.mPrimitiveTypes;
213 tmp.mNumVertices = mesh.mNumVertices;
214 tmp.mVertices = positions.data();
215 tmp.mNormals = normals.data();
216 tmp.mTangents = tangents.empty() ? nullptr : tangents.data();
217 tmp.mBitangents = bitangents.empty() ? nullptr : bitangents.data();
218 tmp.mNumFaces = mesh.mNumFaces;
219 tmp.mFaces = flipWinding ? flippedFaces.data() : mesh.mFaces;
220 tmp.mMaterialIndex = mesh.mMaterialIndex;
221 tmp.mNumAnimMeshes = mesh.mNumAnimMeshes;
222 tmp.mAnimMeshes = mesh.mAnimMeshes;
223 if (mesh.HasTextureCoords(0)) {
224 tmp.mTextureCoords[0] = mesh.mTextureCoords[0];
225 tmp.mNumUVComponents[0] = mesh.mNumUVComponents[0];
226 }
227 const auto detach = [&]() {
228 tmp.mVertices = nullptr;
229 tmp.mNormals = nullptr;
230 tmp.mTangents = nullptr;
231 tmp.mBitangents = nullptr;
232 tmp.mFaces = nullptr;
233 tmp.mTextureCoords[0] = nullptr;
234 tmp.mAnimMeshes = nullptr;
235 };
236 Mesh *out = nullptr;
237 try {
238 out = newMeshFromAssimp(tmp);
239 } catch (...) {
240 detach();
241 throw;
242 }
243 detach();
244 return out;
245}
246
247Mesh *Graphics::newMeshFromArrays(const float *posXYZ, const float *nrmXYZ, const float *uvST,
248 int vertexCount, const uint32_t *indices, int indexCount) {
249 return newMeshFromArraysColored(posXYZ,nrmXYZ,uvST,nullptr,vertexCount,indices,indexCount);
250}
251
252Mesh *Graphics::newMeshFromArraysColored(const float *posXYZ, const float *nrmXYZ, const float *uvST,
253 const float *colorRGBA, int vertexCount,
254 const uint32_t *indices, int indexCount) {
255 ASSERT(initialized);
256 if (!initialized) throw Exception("newMeshFromArrays: graphics not initialized");
257 if (!posXYZ || vertexCount <= 0) throw Exception("newMeshFromArrays: empty positions");
258 if (!indices || indexCount < 3) throw Exception("newMeshFromArrays: empty indices");
259 if (indexCount % 3 != 0) throw Exception("newMeshFromArrays: indexCount must be multiple of 3");
260
261 std::vector<MeshVertex> verts(static_cast<size_t>(vertexCount));
262 for (int i = 0; i < vertexCount; ++i) {
263 MeshVertex &v = verts[static_cast<size_t>(i)];
264 v.pos = {posXYZ[size_t(i) * 3u], posXYZ[size_t(i) * 3u + 1u], posXYZ[size_t(i) * 3u + 2u]};
265 if (nrmXYZ)
266 v.normal = {nrmXYZ[size_t(i) * 3u], nrmXYZ[size_t(i) * 3u + 1u],
267 nrmXYZ[size_t(i) * 3u + 2u]};
268 else
269 v.normal = {0.f, 1.f, 0.f};
270 if (uvST)
271 v.uv = {uvST[size_t(i) * 2u], uvST[size_t(i) * 2u + 1u]};
272 else
273 v.uv = {0.f, 0.f};
274 if (colorRGBA)
275 v.color = {colorRGBA[size_t(i)*4u],colorRGBA[size_t(i)*4u+1u],
276 colorRGBA[size_t(i)*4u+2u],colorRGBA[size_t(i)*4u+3u]};
277 }
278
279 std::vector<uint32_t> idx(indices, indices + indexCount);
280 for (uint32_t id : idx) {
281 if (int(id) >= vertexCount) throw Exception("newMeshFromArrays: index out of range");
282 }
283
284 auto gpu = uploadGpuMesh(device, frameToken(), verts, idx);
285 auto handle = makeMeshHandle(*gpu);
286 Mesh *raw = handle.get();
287 raw->computeBounds(posXYZ, vertexCount);
288 registerMeshRecord(gpu.get());
289 ownedGpuMeshes.push_back(std::move(gpu));
290 ownedMeshes.push_back(std::move(handle));
291 return raw;
292}
293
294std::optional<eve::graphics::MeshBackendDescriptor> Graphics::describeMesh(Mesh *mesh) const {
295 if (!mesh || !mesh->gpuHandle) return std::nullopt;
296 const auto *gpu = static_cast<const GpuMesh *>(mesh->gpuHandle);
297 const auto owned =
298 std::find_if(ownedGpuMeshes.begin(), ownedGpuMeshes.end(),
299 [gpu](const std::unique_ptr<GpuMesh> &candidate) { return candidate.get() == gpu; });
300 if (owned == ownedGpuMeshes.end()) return std::nullopt;
301 return eve::graphics::MeshBackendDescriptor{gpu->vertexCount, gpu->indexCount,
302 static_cast<std::uint32_t>(sizeof(MeshVertex)),
303 gpu->indexType == vk::IndexType::eUint16 ? 2u : 4u};
304}
305
307 if (!mesh || !mesh->gpuHandle || !mesh->hasMorphData() || !mesh->isMorphDirty()) return false;
308 if (!initialized) return false;
309
310 std::vector<float> pos;
311 std::vector<float> nrm;
312 mesh->computeMorphedPositions(pos, nrm);
313 const int vc = mesh->getVertexCount();
314 if (vc <= 0 || int(pos.size()) < vc * 3) return false;
315 mesh->computeBounds(pos.data(), vc);
316
317 std::vector<MeshVertex> verts(static_cast<size_t>(vc));
318 auto *gpu = static_cast<GpuMesh *>(mesh->gpuHandle);
319 if (mesh->hasGpuSkinning()) {
320 // Morphs change positions/normals only; retain the authored skin influences.
321 auto &source = meshDrawVertices(*gpu);
322 void *mapped = source.map();
323 if (!mapped) return false;
324 std::memcpy(verts.data(), mapped, verts.size() * sizeof(MeshVertex));
325 source.unmap();
326 }
327 const auto &uv = mesh->baseUv();
328 for (int i = 0; i < vc; ++i) {
329 MeshVertex &v = verts[static_cast<size_t>(i)];
330 v.pos = {pos[size_t(i) * 3u + 0], pos[size_t(i) * 3u + 1], pos[size_t(i) * 3u + 2]};
331 if (int(nrm.size()) >= (i + 1) * 3)
332 v.normal = {nrm[size_t(i) * 3u + 0], nrm[size_t(i) * 3u + 1], nrm[size_t(i) * 3u + 2]};
333 else
334 v.normal = {0.f, 1.f, 0.f};
335 if (int(uv.size()) >= (i + 1) * 2)
336 v.uv = {uv[size_t(i) * 2u + 0], uv[size_t(i) * 2u + 1]};
337 else
338 v.uv = {0.f, 0.f};
339 }
340
341 // Ring-buffered host-visible VBO: the next copy is kDynamicVertexCopies
342 // frames old, so overwriting it never races with in-flight draws — no
343 // device-wide wait (see writeDynamicMesh).
344 ensureDynamicRing(*gpu);
345 writeDynamicMesh(*gpu, verts, getDevice(), frameToken(), nullptr, 0);
346 mesh->markMorphClean();
347 return true;
348}
349
350bool Graphics::updateMeshVertices(Mesh *mesh, const float *posXYZ, const float *nrmXYZ,
351 const float *uvST, int vertexCount, const uint32_t *indices,
352 int indexCount) {
353 if (!initialized || !mesh || !mesh->gpuHandle) return false;
354 if (!posXYZ || vertexCount <= 0 || indexCount < 0) return false;
355 if (indexCount > 0 && (indexCount % 3 != 0 || !indices)) return false;
356 for (int i = 0; i < indexCount; ++i) {
357 if (indices[i] >= uint32_t(vertexCount)) return false;
358 }
359
360 std::vector<MeshVertex> verts(static_cast<size_t>(vertexCount));
361 for (int i = 0; i < vertexCount; ++i) {
362 MeshVertex &v = verts[static_cast<size_t>(i)];
363 v.pos = {posXYZ[size_t(i) * 3u], posXYZ[size_t(i) * 3u + 1u], posXYZ[size_t(i) * 3u + 2u]};
364 if (nrmXYZ)
365 v.normal = {nrmXYZ[size_t(i) * 3u], nrmXYZ[size_t(i) * 3u + 1u],
366 nrmXYZ[size_t(i) * 3u + 2u]};
367 else
368 v.normal = {0.f, 1.f, 0.f};
369 if (uvST)
370 v.uv = {uvST[size_t(i) * 2u], uvST[size_t(i) * 2u + 1u]};
371 else
372 v.uv = {0.f, 0.f};
373 }
374
375 auto *gpu = static_cast<GpuMesh *>(mesh->gpuHandle);
376 mesh->computeBounds(posXYZ, vertexCount);
377 // Same ring-buffer approach as bakeMeshMorph: never wait on in-flight
378 // frames, just write the next copy.
379 ensureDynamicRing(*gpu);
380 writeDynamicMesh(*gpu, verts, getDevice(), frameToken(), indices, indexCount);
381 mesh->gpuVertexCount = int(gpu->vertexCount);
382 mesh->indexCount = int(gpu->indexCount);
383 return true;
384}
385
386bool Graphics::setMeshSkinningData(Mesh *mesh, const uint16_t *joints4, const float *weights4,
387 int vertexCount) {
388 if (!initialized || !mesh || !mesh->gpuHandle || !joints4 || !weights4) return false;
389 auto *gpu = static_cast<GpuMesh *>(mesh->gpuHandle);
390 if (vertexCount <= 0 || uint32_t(vertexCount) != gpu->vertexCount) return false;
391
392 std::vector<MeshVertex> verts(static_cast<size_t>(vertexCount));
393 auto &source = meshDrawVertices(*gpu);
394 void *mapped = source.map();
395 if (!mapped) return false;
396 std::memcpy(verts.data(), mapped, verts.size() * sizeof(MeshVertex));
397 source.unmap();
398 for (int i = 0; i < vertexCount; ++i) {
399 const size_t base = static_cast<size_t>(i) * 4u;
400 verts[static_cast<size_t>(i)].joints =
401 glm::u16vec4(joints4[base], joints4[base + 1], joints4[base + 2], joints4[base + 3]);
402 verts[static_cast<size_t>(i)].weights =
403 glm::vec4(weights4[base], weights4[base + 1], weights4[base + 2], weights4[base + 3]);
404 }
405 ensureDynamicRing(*gpu);
406 writeDynamicMesh(*gpu, verts, getDevice(), frameToken(), nullptr, 0);
407 mesh->markGpuSkinned(true);
408 return true;
409}
410
412 if (!mesh || !mesh->gpuHandle) return false;
413
414 auto *gpu = static_cast<GpuMesh *>(mesh->gpuHandle);
415 auto gpuIt = std::find_if(ownedGpuMeshes.begin(), ownedGpuMeshes.end(),
416 [&](const std::unique_ptr<GpuMesh> &g) {
417 return g.get() == gpu;
418 });
419 if (gpuIt == ownedGpuMeshes.end()) return false;
420
421 auto meshIt = std::find_if(ownedMeshes.begin(), ownedMeshes.end(),
422 [&](const std::unique_ptr<Mesh> &m) {
423 return m.get() == mesh;
424 });
425 if (meshIt == ownedMeshes.end()) return false;
426
427 // An in-flight draw may still read the vertex/index buffers; drain first.
428 waitForSharedGpuResources();
429 mesh->gpuHandle = nullptr;
430 ownedGpuMeshes.erase(gpuIt);
431 // Transfer the CPU facade to the caller instead of destroying it.
432 (void)meshIt->release();
433 ownedMeshes.erase(meshIt);
434 return true;
435}
436
437Mesh *Graphics::newMeshSphere(int slices, int stacks) {
438 ASSERT(initialized);
439 if (!initialized) throw Exception("newMeshSphere: graphics not initialized");
440 if (slices < 3) slices = 3;
441 if (stacks < 2) stacks = 2;
442 if (slices > 256) slices = 256;
443 if (stacks > 128) stacks = 128;
444
445 constexpr float kPi = 3.14159265358979323846f;
446 constexpr float kTwoPi = kPi * 2.f;
447
448 // Layout (stride = slices+1, seam column duplicated for continuous U):
449 // row 0: north pole verts (same pos, unique U)
450 // row 1..stacks-1: latitude rings
451 // row stacks: south pole verts
452 // One pole vertex per longitude avoids a single-fan UV singularity and
453 // keeps every triangle non-degenerate.
454 const int stride = slices + 1;
455 const int rows = stacks + 1; // includes both pole rows
456 std::vector<MeshVertex> verts;
457 verts.reserve(size_t(stride) * size_t(rows));
458
459 auto pushVert = [&](float px, float py, float pz, float u, float v) {
461 vert.pos = {px, py, pz};
462 vert.normal = {px, py, pz}; // unit sphere
463 vert.uv = {u, v};
464 verts.push_back(vert);
465 };
466
467 for (int y = 0; y <= stacks; ++y) {
468 const float fv = float(y) / float(stacks);
469 const float phi = fv * kPi;
470 const float sinPhi = std::sin(phi);
471 const float cosPhi = std::cos(phi);
472 for (int x = 0; x <= slices; ++x) {
473 const float u = float(x) / float(slices);
474 const float theta = u * kTwoPi;
475 // Exact poles: collapse ring to a point but keep per-slice UVs.
476 if (y == 0)
477 pushVert(0.f, 1.f, 0.f, u, 0.f);
478 else if (y == stacks)
479 pushVert(0.f, -1.f, 0.f, u, 1.f);
480 else
481 pushVert(sinPhi * std::cos(theta), cosPhi, sinPhi * std::sin(theta), u, fv);
482 }
483 }
484
485 std::vector<uint32_t> indices;
486 indices.reserve(size_t(slices) * size_t(stacks) * 6u);
487
488 // Quads between consecutive rows use outward object-space CCW winding.
489 for (int y = 0; y < stacks; ++y) {
490 const uint32_t row0 = uint32_t(y * stride);
491 const uint32_t row1 = uint32_t((y + 1) * stride);
492 for (int x = 0; x < slices; ++x) {
493 const uint32_t i0 = row0 + uint32_t(x);
494 const uint32_t i1 = row0 + uint32_t(x + 1);
495 const uint32_t i2 = row1 + uint32_t(x);
496 const uint32_t i3 = row1 + uint32_t(x + 1);
497 indices.push_back(i0);
498 indices.push_back(i1);
499 indices.push_back(i2);
500 indices.push_back(i1);
501 indices.push_back(i3);
502 indices.push_back(i2);
503 }
504 }
505
506 auto gpu = uploadGpuMesh(device, frameToken(), verts, indices);
507 auto handle = makeMeshHandle(*gpu);
508 Mesh *raw = handle.get();
509 assignMeshBounds(raw, verts);
510 registerMeshRecord(gpu.get());
511 ownedGpuMeshes.push_back(std::move(gpu));
512 ownedMeshes.push_back(std::move(handle));
513 return raw;
514}
515
516Mesh *Graphics::newMeshCylinder(int slices, int stacks, bool caps) {
517 ASSERT(initialized);
518 if (!initialized) throw Exception("newMeshCylinder: graphics not initialized");
519 if (slices < 3) slices = 3;
520 if (stacks < 1) stacks = 1;
521 if (slices > 256) slices = 256;
522 if (stacks > 128) stacks = 128;
523
524 constexpr float kPi = 3.14159265358979323846f;
525 constexpr float kTwoPi = kPi * 2.f;
526 constexpr float kRadius = 1.f;
527 constexpr float kHalfH = 1.f; // height 2, Y from -1..1
528
529 const int stride = slices + 1; // duplicated seam for continuous U
530 const int sideRows = stacks + 1;
531 std::vector<MeshVertex> verts;
532 verts.reserve(size_t(stride) * size_t(sideRows) + size_t(caps ? 2 * (slices + 2) : 0));
533
534 auto pushVert = [&](float px, float py, float pz, float nx, float ny, float nz, float u,
535 float v) {
537 vert.pos = {px, py, pz};
538 vert.normal = {nx, ny, nz};
539 vert.uv = {u, v};
540 verts.push_back(vert);
541 };
542
543 // Side wall: outward normals in XZ.
544 for (int y = 0; y <= stacks; ++y) {
545 const float fv = float(y) / float(stacks);
546 const float py = kHalfH - fv * (2.f * kHalfH);
547 for (int x = 0; x <= slices; ++x) {
548 const float u = float(x) / float(slices);
549 const float theta = u * kTwoPi;
550 const float cx = std::cos(theta);
551 const float sz = std::sin(theta);
552 pushVert(kRadius * cx, py, kRadius * sz, cx, 0.f, sz, u, fv);
553 }
554 }
555
556 std::vector<uint32_t> indices;
557 indices.reserve(size_t(slices) * size_t(stacks) * 6u +
558 size_t(caps ? slices * 2 * 3 : 0));
559
560 for (int y = 0; y < stacks; ++y) {
561 const uint32_t row0 = uint32_t(y * stride);
562 const uint32_t row1 = uint32_t((y + 1) * stride);
563 for (int x = 0; x < slices; ++x) {
564 const uint32_t i0 = row0 + uint32_t(x);
565 const uint32_t i1 = row0 + uint32_t(x + 1);
566 const uint32_t i2 = row1 + uint32_t(x);
567 const uint32_t i3 = row1 + uint32_t(x + 1);
568 indices.push_back(i0);
569 indices.push_back(i1);
570 indices.push_back(i2);
571 indices.push_back(i1);
572 indices.push_back(i3);
573 indices.push_back(i2);
574 }
575 }
576
577 if (caps) {
578 // Top cap (y = +1, normal +Y) — fan from center.
579 const uint32_t topCenter = uint32_t(verts.size());
580 pushVert(0.f, kHalfH, 0.f, 0.f, 1.f, 0.f, 0.5f, 0.5f);
581 const uint32_t topRing = uint32_t(verts.size());
582 for (int x = 0; x <= slices; ++x) {
583 const float u = float(x) / float(slices);
584 const float theta = u * kTwoPi;
585 const float cx = std::cos(theta);
586 const float sz = std::sin(theta);
587 pushVert(kRadius * cx, kHalfH, kRadius * sz, 0.f, 1.f, 0.f, 0.5f + 0.5f * cx,
588 0.5f + 0.5f * sz);
589 }
590 for (int x = 0; x < slices; ++x) {
591 indices.push_back(topCenter);
592 indices.push_back(topRing + uint32_t(x + 1));
593 indices.push_back(topRing + uint32_t(x));
594 }
595
596 // Bottom cap (y = -1, normal -Y).
597 const uint32_t botCenter = uint32_t(verts.size());
598 pushVert(0.f, -kHalfH, 0.f, 0.f, -1.f, 0.f, 0.5f, 0.5f);
599 const uint32_t botRing = uint32_t(verts.size());
600 for (int x = 0; x <= slices; ++x) {
601 const float u = float(x) / float(slices);
602 const float theta = u * kTwoPi;
603 const float cx = std::cos(theta);
604 const float sz = std::sin(theta);
605 pushVert(kRadius * cx, -kHalfH, kRadius * sz, 0.f, -1.f, 0.f, 0.5f + 0.5f * cx,
606 0.5f + 0.5f * sz);
607 }
608 for (int x = 0; x < slices; ++x) {
609 indices.push_back(botCenter);
610 indices.push_back(botRing + uint32_t(x));
611 indices.push_back(botRing + uint32_t(x + 1));
612 }
613 }
614
615 auto gpu = uploadGpuMesh(device, frameToken(), verts, indices);
616 auto handle = makeMeshHandle(*gpu);
617 Mesh *raw = handle.get();
618 assignMeshBounds(raw, verts);
619 registerMeshRecord(gpu.get());
620 ownedGpuMeshes.push_back(std::move(gpu));
621 ownedMeshes.push_back(std::move(handle));
622 return raw;
623}
624
625void Graphics::drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint) {
626 drawMeshShader(mesh, model, texture, tint, nullptr);
627}
628
629void Graphics::drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint,
630 Shader *shader) {
631 drawMeshShaderRange(mesh, model, texture, tint, shader, 0, 1);
632}
633
635 uint32_t first, uint32_t count) {
636 auto fail = [](const char *message) {
638 };
639 auto ownedShader = std::find_if(ownedGpuShaders.begin(), ownedGpuShaders.end(),
640 [&](const auto &item) { return item->owner == &shader; });
641 auto ownedMesh =
642 std::find_if(ownedMeshes.begin(), ownedMeshes.end(), [&](const auto &item) { return item.get() == &mesh; });
643 if (!initialized || ownedShader == ownedGpuShaders.end() || ownedMesh == ownedMeshes.end() ||
644 !(*ownedShader)->isMesh3D || (*ownedShader)->isHair3D || shader.isXray() || !mesh.gpuHandle ||
645 (!swapchainPassOpen && !offscreen3DPassOpen))
646 return fail("Expected owned mesh resources inside an open 3D pass");
647 const auto size = meshResourceInstanceCount(**ownedShader);
648 if (!size || first > size || count > size - first)
649 return fail("Instance range exceeds the immutable matrix buffer");
650 for (int col = 0; col < 4; ++col)
651 for (int row = 0; row < 4; ++row)
652 if (!std::isfinite(model[col][row])) return fail("Nonfinite model matrix");
653 for (int i = 0; i < 4; ++i)
654 if (!std::isfinite(tint[i])) return fail("Nonfinite instance tint");
655 if (!count) return Result<void>::success();
656 try {
657 drawMeshShaderRange(&mesh, model, nullptr, tint, &shader, first, count);
658 return Result<void>::success();
659 } catch (const std::exception &error) {
660 return Result<void>::failure(Diagnostic::error(DiagnosticCode::Failed, error.what(), "graphics.instances"));
661 }
662}
663
664void Graphics::drawMeshShaderRange(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint,
665 Shader *shader, uint32_t firstInstance, uint32_t instanceCount) {
666 ASSERT(initialized);
667 ASSERT(mesh != nullptr);
668 if (!initialized) throw Exception("drawMesh: graphics not initialized");
669 if (!mesh || !mesh->gpuHandle) throw Exception("drawMesh: null mesh");
670 if (!swapchainPassOpen && !offscreen3DPassOpen)
671 throw Exception("drawMesh: call begin3DFrame first");
672 createMesh3DPipeline();
673 if (!mesh3dPipeline) throw Exception("drawMesh: mesh3d pipeline missing");
674
675 if (shader) {
676 if (shader->getKind() != Shader::Kind::eMesh3D)
677 throw Exception("drawMesh: shader is not a Mesh3D shader (use newMeshShader*)");
678 if (!shader->gpuHandle) throw Exception("drawMesh: shader has no GPU pipeline");
679 }
680
681 if (pbrSurface_ && !shader) {
682 drawPbrMesh(mesh, model, tint);
683 return;
684 }
685 auto *gpuMesh = static_cast<GpuMesh *>(mesh->gpuHandle);
686 Texture *tex = texture ? texture : whiteTexture;
687 if (!tex || !tex->gpuHandle) throw Exception("drawMesh: missing texture");
688 auto *gpuTex = static_cast<GpuTexture *>(tex->gpuHandle);
689
690 ensureFlatNormalTexture3D();
691 Texture *ntex = mesh3dNormalTexture ? mesh3dNormalTexture : flatNormalTexture3D;
692 if (!ntex || !ntex->gpuHandle) throw Exception("drawMesh: missing normal texture");
693 auto *gpuNormal = static_cast<GpuTexture *>(ntex->gpuHandle);
694
695 ensureFlatHeightTexture3D();
696 Texture *htex = mesh3dHeightTexture ? mesh3dHeightTexture : flatHeightTexture3D;
697 if (!htex || !htex->gpuHandle) throw Exception("drawMesh: missing height texture");
698 auto *gpuHeight = static_cast<GpuTexture *>(htex->gpuHandle);
699
700 Texture *depthTex = mesh3dSceneDepthTexture ? mesh3dSceneDepthTexture : whiteTexture;
701 if (!depthTex || !depthTex->gpuHandle) throw Exception("drawMesh: missing scene depth texture");
702 auto *gpuDepth = static_cast<GpuTexture *>(depthTex->gpuHandle);
703
704 // Screen-space decal layer (bindings 8/9/10). Only sampled when the decal
705 // pass actually recorded this frame; otherwise the transparent/flat
706 // placeholders make the blend a no-op.
707 ensureDecalPlaceholders();
708 GpuTexture *gpuDecalAlb = nullptr;
709 GpuTexture *gpuDecalNrm = nullptr;
710 GpuTexture *gpuDecalPrm = nullptr;
711 if (decalLayerFresh) {
712 if (auto *dslot = currentDecalSlot()) {
713 gpuDecalAlb = &dslot->albedoGpu;
714 gpuDecalNrm = &dslot->normalGpu;
715 gpuDecalPrm = &dslot->paramsGpu;
716 }
717 }
718 if (!gpuDecalAlb) {
719 gpuDecalAlb = static_cast<GpuTexture *>(decalFlatAlbedo->gpuHandle);
720 gpuDecalNrm = static_cast<GpuTexture *>(decalFlatNormal->gpuHandle);
721 gpuDecalPrm = static_cast<GpuTexture *>(decalFlatParams->gpuHandle);
722 }
723
724 ensureDefaultEnvCubemap();
725 Texture *envTex = mesh3dEnvTexture ? mesh3dEnvTexture : defaultEnvCubemap;
726 if (!envTex || !envTex->gpuHandle) throw Exception("drawMesh: missing env cubemap");
727 auto *gpuEnv = static_cast<GpuTexture *>(envTex->gpuHandle);
728 if (!gpuEnv->isCube) throw Exception("drawMesh: env texture is not a cubemap");
729 const float envIntensity = (mesh3dEnvTexture && mesh3dEnvIntensity > 0.f) ? mesh3dEnvIntensity : 0.f;
730
731 const bool useClustered = mesh3dClusteredActive && !shader && mesh3dClusteredPipeline &&
732 mesh3dSurfaceMode != SurfaceMode::Transparent && !mesh->hasGpuSkinning();
733 auto &cb = currentPresentCb();
734
735 auto makeShadowUbo = [&]() {
736 ShadowUBO s = mesh3dShadows.ubo;
737 if (!mesh3dShadows.active) {
738 s.bias.y = 0.f;
739 s.splits.w = 0.f;
740 }
741 s.bias.z = mesh3dShadowReceive ? 1.f : 0.f;
742 return s;
743 };
744
745 if (useClustered) {
746 Mesh3DClusteredUBO ubo{};
747 ubo.model = model;
748 ubo.mvp = mesh3dFrameUbo.mvp * model;
749 ubo.view = mesh3dClustered.view;
750 ubo.lightDir = mesh3dClustered.primaryDir;
751 ubo.lightColor = glm::vec4(glm::vec3(mesh3dClustered.primaryColor), envIntensity);
752 ubo.tint = glm::vec4(tint.r, tint.g, tint.b, tint.a);
753 ubo.cameraPos = glm::vec4(glm::vec3(mesh3dFrameUbo.cameraPos), mesh3dRoughness);
754 ubo.ambient = glm::vec4(glm::vec3(mesh3dClustered.ambient), mesh3dMetallic);
755 ubo.gridInfo = mesh3dClustered.gridInfo;
756 ubo.clipInfo = mesh3dClustered.clipInfo;
757 float surfaceCode = float(int(mesh3dSurfaceMode));
758 if (mesh3dSurfaceMode == SurfaceMode::Masked && mesh3dAlphaTechnique == "dither")
759 surfaceCode = 3.f;
760 else if (mesh3dSurfaceMode == SurfaceMode::Masked &&
761 mesh3dAlphaTechnique == "coverage")
762 surfaceCode = 4.f;
763 ubo.texBomb = glm::vec4(mesh3dTexBombScale, mesh3dTexBombStrength, mesh3dTexBombRot,
764 surfaceCode);
765 ubo.parallax =
766 glm::vec4(mesh3dParallaxScale, mesh3dParallaxMinLayers, mesh3dParallaxMaxLayers,
767 mesh3dAlphaCutoff);
768 ubo.virtualTexture = mesh3dVirtualTexture;
769 ubo.virtualAtlas = mesh3dVirtualAtlas;
770 ubo.envProbeCenter = glm::vec4(mesh3dEnvProbeCenter, 1.f);
771 ubo.envProbeExtent = glm::vec4(mesh3dEnvProbeExtent, 0.f);
772 for (int i = 0; i < ReflectionProbeUpload::kMaxProbes; ++i) {
773 if (i >= mesh3dReflectionProbes.count) continue;
774 const auto &probe = mesh3dReflectionProbes.probes[i];
775 if (!probe.cubemap || !probe.cubemap->gpuHandle ||
776 !static_cast<GpuTexture *>(probe.cubemap->gpuHandle)->isCube)
777 continue;
778 ubo.reflectionProbeCenter[i] = glm::vec4(probe.center, probe.intensity);
779 ubo.reflectionProbeExtent[i] = glm::vec4(probe.extent, probe.blendDistance);
780 }
781
782 auto &cfslots = currentMesh3dClusteredFrameSlots();
783 if (cfslots.drawIndex >= cfslots.capacity) {
784 std::fprintf(stderr,
785 "[vulkan] clustered mesh3d UBO ring exhausted (%zu draws); draw skipped\n",
786 cfslots.capacity);
787 return;
788 }
789 const size_t slot = cfslots.drawIndex++;
790 ensureMesh3dStrides();
791 const uint32_t uboOffset = uint32_t(slot) * mesh3dClusteredUboStride;
792 const uint32_t shadowOffset = uint32_t(slot) * shadowUboStride;
793 updateRingLocal(cfslots.uboRing, uboOffset, &ubo, sizeof(ubo));
794 const ShadowUBO shadow = makeShadowUbo();
795 updateRingLocal(cfslots.shadowRing, shadowOffset, &shadow, sizeof(shadow));
796 vk::DescriptorSet set =
797 mesh3dClusteredSetFor(gpuTex, gpuNormal, gpuEnv, gpuHeight, gpuDecalAlb, gpuDecalNrm,
798 gpuDecalPrm, cfslots);
799 const uint32_t dynOffsets[2] = {uboOffset, shadowOffset};
800
801 if (mesh3dClusteredPipeline != lastMesh3dClusteredPipeline) {
802 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, mesh3dClusteredPipeline);
803 lastMesh3dClusteredPipeline = mesh3dClusteredPipeline;
804 }
805 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dClusteredPipelineLayout, 0, 1,
806 &set, 2, dynOffsets);
807 drawIndexedMesh(cb, *gpuMesh);
808 return;
809 }
810
811 Mesh3DUBO ubo = mesh3dFrameUbo;
812 ubo.model = model;
813 ubo.mvp = mesh3dFrameUbo.mvp * model;
814 ubo.tint = glm::vec4(tint.r, tint.g, tint.b, tint.a);
815 ubo.ambient = glm::vec4(glm::vec3(mesh3dLighting.ambient), mesh3dMetallic);
816 const int lightCount = std::max(0, std::min(mesh3dLighting.count, Lighting3DPack::kMaxLights));
817 ubo.lightDir.w = float(lightCount);
818 ubo.cameraPos.w = mesh3dRoughness;
819 ubo.lightColor.w = envIntensity;
820 float surfaceCode = float(int(mesh3dSurfaceMode));
821 if (mesh3dSurfaceMode == SurfaceMode::Masked && mesh3dAlphaTechnique == "dither")
822 surfaceCode = 3.f;
823 else if (mesh3dSurfaceMode == SurfaceMode::Masked && mesh3dAlphaTechnique == "coverage")
824 surfaceCode = 4.f;
825 ubo.texBomb = glm::vec4(mesh3dTexBombScale, mesh3dTexBombStrength, mesh3dTexBombRot,
826 surfaceCode);
827 ubo.parallax =
828 glm::vec4(mesh3dParallaxScale, mesh3dParallaxMinLayers, mesh3dParallaxMaxLayers,
829 mesh3dAlphaCutoff);
830 ubo.virtualTexture = mesh3dVirtualTexture;
831 ubo.virtualAtlas = mesh3dVirtualAtlas;
832 ubo.lodFade = mesh3dLodFade;
833 for (int i = 0; i < ReflectionProbeUpload::kMaxProbes; ++i) {
834 if (i >= mesh3dReflectionProbes.count) continue;
835 const auto &probe = mesh3dReflectionProbes.probes[i];
836 if (!probe.cubemap || !probe.cubemap->gpuHandle ||
837 !static_cast<GpuTexture *>(probe.cubemap->gpuHandle)->isCube)
838 continue;
839 ubo.reflectionProbeCenter[i] = glm::vec4(probe.center, probe.intensity);
840 ubo.reflectionProbeExtent[i] = glm::vec4(probe.extent, probe.blendDistance);
841 }
842 if (mesh->hasGpuSkinning()) {
843 ubo.skinInfo.x = static_cast<float>(mesh->getSkinPaletteCount());
844 ubo.skinInfo.y = static_cast<float>(mesh3dSkinInfluenceLimit);
845 }
846 for (int i = 0; i < lightCount; ++i) ubo.lights[i] = mesh3dLighting.lights[i];
847 int dirI = -1;
848 for (int i = 0; i < lightCount; ++i) {
849 if (mesh3dLighting.lights[i].posRadius.w <= 0.f) {
850 dirI = i;
851 break;
852 }
853 }
854 if (dirI >= 0) {
855 glm::vec3 d(mesh3dLighting.lights[dirI].posRadius);
856 if (glm::length(d) < 1e-6f) d = glm::vec3(0.f, 1.f, 0.f);
857 else d = glm::normalize(d);
858 ubo.lightDir = glm::vec4(d, float(lightCount));
859 ubo.lightColor = glm::vec4(glm::vec3(mesh3dLighting.lights[dirI].color), envIntensity);
860 } else {
861 ubo.lightDir = glm::vec4(0.f, 1.f, 0.f, float(lightCount));
862 ubo.lightColor = glm::vec4(0.f, 0.f, 0.f, envIntensity);
863 }
864
865 auto &fslots = currentMesh3dFrameSlots();
866 if (fslots.drawIndex >= fslots.capacity) {
867 std::fprintf(stderr, "[vulkan] mesh3d UBO ring exhausted (%zu draws); draw skipped\n",
868 fslots.capacity);
869 return;
870 }
871 uploadSkinPalette(mesh, fslots);
872 const size_t slot = fslots.drawIndex++;
873 ensureMesh3dStrides();
874 const uint32_t uboOffset = uint32_t(slot) * mesh3dUboStride;
875 const uint32_t shadowOffset = uint32_t(slot) * shadowUboStride;
876 updateRingLocal(fslots.uboRing, uboOffset, &ubo, sizeof(ubo));
877 const ShadowUBO shadow = makeShadowUbo();
878 updateRingLocal(fslots.shadowRing, shadowOffset, &shadow, sizeof(shadow));
879 auto *gpuSceneColor = mesh3dSceneColorTexture && mesh3dSceneColorTexture->gpuHandle
880 ? static_cast<GpuTexture *>(mesh3dSceneColorTexture->gpuHandle)
881 : sceneColorHistoryValid && completedSceneColorSlot < sceneColorSlots.size()
882 ? &sceneColorSlots[completedSceneColorSlot].colorGpu
883 : static_cast<GpuTexture *>(whiteTexture->gpuHandle);
884 vk::DescriptorSet set = mesh3dSetFor(gpuTex, gpuNormal, gpuEnv, gpuHeight, gpuDepth, gpuSceneColor,
885 gpuDecalAlb, gpuDecalNrm, gpuDecalPrm, fslots);
886 const uint32_t dynOffsets[2] = {uboOffset, shadowOffset};
887
888 if (shader) {
889 auto *gs = static_cast<GpuShader *>(shader->gpuHandle);
890 vk::Pipeline activePipeline = offscreen3DPassOpen
891 ? (offscreen3DHDRActive
892 ? gs->mesh3dHdrOffscreenPipeline
893 : gs->mesh3dOffscreenPipeline)
894 : gs->mesh3dPipeline;
895 if (shader->isXray() && !offscreen3DPassOpen) {
896 // X-ray silhouette pass: depth test/write off + alpha blend so the
897 // occluded part paints over the building. The pipeline is created
898 // with the shader (see newMeshShaderFromSpv); do not compile it
899 // here — a render pass is already open.
900 activePipeline = gs->mesh3dXrayPipeline;
901 }
902 if (!activePipeline) return;
903 if (activePipeline != lastMesh3dPipeline) {
904 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, activePipeline);
905 lastMesh3dPipeline = activePipeline;
906 }
907 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, gs->pipelineLayout, 0, 1, &set, 2, dynOffsets);
908 if (gs->resources)
909 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, gs->pipelineLayout, 1, 1, &gs->resourceSet, 0,
910 nullptr);
911 cb.pushConstants(gs->pipelineLayout, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0,
912 Shader::kPushConstantBytes, shader->pushConstantData());
913 } else {
914 const bool transparent = mesh3dSurfaceMode == SurfaceMode::Transparent;
915 const BlendMode blend = transparent ? mesh3dSurfaceBlend : BlendMode::Opaque;
916 const bool depthWrite = !transparent || mesh3dSurfaceDepthWrite;
917 const size_t pipelineIndex =
918 mesh3dPipelineIndex(blend, depthWrite, mesh3dSurfaceDoubleSided);
919 const vk::Pipeline pipe = offscreen3DPassOpen
920 ? (offscreen3DHDRActive
921 ? hdrOffscreen3DSurfacePipelines[pipelineIndex]
922 : offscreen3DSurfacePipelines[pipelineIndex])
923 : mesh3dSurfacePipelines[pipelineIndex];
924 if (pipe != lastMesh3dPipeline) {
925 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, pipe);
926 lastMesh3dPipeline = pipe;
927 }
928 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dPipelineLayout, 0, 1, &set, 2,
929 dynOffsets);
930 }
931 drawIndexedMesh(cb, *gpuMesh, instanceCount, firstInstance);
932}
933
934
935} // namespace eve::graphics::vulkan
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
ActiveSource owned
const std::string & s
std::vector< std::uint32_t > verts
Definition Builder.cpp:27
float cx
Definition CardTypes.cpp:33
float uv
Vec3 tangent
Definition CaveMesh.cpp:80
float py
float nx
float nz
float ny
float pz
int rows
std::string message
std::uint32_t vertexCount
std::uint32_t indexCount
tensor::Graph g
Definition GpuGraph.cpp:7
vkb::Device & device
vk::ShaderModule vert
std::uint32_t firstInstance
std::uint32_t instanceCount
glm::vec4 tint
uint32_t i1
Definition Grass.cpp:61
uint32_t i2
Definition Grass.cpp:61
uint32_t i0
Definition Grass.cpp:61
float u
Definition Grass.cpp:233
std::vector< std::uint32_t > indices
std::vector< float > normals
std::vector< float > positions
float v
std::int32_t first
float blend
std::vector< Colorf > px
MeleePoint3 b
Definition MeleeHit.cpp:41
std::string error
Definition Package.cpp:60
int idx
float f
PrimitiveHandle handle
float d
float t
Mesh * mesh
Shader * shader
glm::mat4 model
std::uint32_t count
float weights[3]
float size
Definition TreeMesh.cpp:156
int caps
Definition TreeMesh.cpp:162
const UnitySourceAsset & source
std::vector< double > phi
float m[16]
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
virtual bool releaseMesh(Mesh *mesh)
Eagerly releases a mesh created by this Graphics.
Definition Graphics.h:927
virtual Mesh * newMeshCylinder(int slices=32, int stacks=1, bool caps=true)=0
Procedural Y-up cylinder (radius 1, height 2 centered at origin). slices = longitude divisions; stack...
virtual void drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint)=0
Draw one mesh with model matrix. Requires begin3DFrame() (or an open swapchain pass).
virtual Mesh * newMeshFromAssimp(const ::aiMesh &mesh)=0
Creates a mesh from assimp. @ownership Caller deletes unless documented otherwise.
virtual std::optional< MeshBackendDescriptor > describeMesh(Mesh *mesh) const
Describe a live mesh created by this Graphics backend.
Definition Graphics.h:859
virtual bool updateMeshVertices(Mesh *mesh, const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount)=0
In-place update of a mesh's vertex/index data (CPU -> host-visible VBO). Mirrors bakeMeshMorph: the u...
virtual Result< void > drawMeshShaderInstances(Mesh &mesh, Shader &shader, const glm::mat4 &model, const Color &tint, std::uint32_t first, std::uint32_t count)
Draw a checked range from a resource shader's immutable instance matrix buffer. @ownership Mesh and S...
Definition Graphics.h:1116
virtual bool setMeshSkinningData(Mesh *mesh, const uint16_t *joints4, const float *weights4, int vertexCount)
Upload four joint indices and weights per vertex for built-in GPU skinning.
Definition Graphics.h:882
virtual bool bakeMeshMorph(Mesh *mesh)=0
If mesh morph weights are dirty, bake blended positions and upload to the GPU VBO....
virtual void drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint, Shader *shader)=0
Draw mesh with an explicit Mesh3D Shader (nullptr = default PBR pipeline).
virtual Mesh * newMeshFromArrays(const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount)=0
Upload a triangle mesh from packed CPU arrays. Owned by Graphics. posXYZ required (vertexCount*3)....
virtual Mesh * newMeshFromArraysColored(const float *posXYZ, const float *nrmXYZ, const float *uvST, const float *colorRGBA, int vertexCount, const uint32_t *indices, int indexCount)=0
Upload a triangle mesh with an optional packed RGBA color per vertex.
virtual Mesh * newMeshSphere(int slices=32, int stacks=16)=0
Procedural UV sphere (radius 1, Y-up). Owned by Graphics. slices = longitude divisions,...
GPU mesh handle (+ optional CPU morph targets).
Definition Mesh.h:25
void computeBounds(const float *posXYZ, int vertexCount)
Compute the bounding sphere (centroid + max radius) from positions.
Definition Mesh.cpp:93
Custom GPU program.
Definition Shader.h:39
static constexpr uint32_t kPushConstantBytes
Definition Shader.h:43
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Definition Texture.h:18
Mesh * newMeshFromAssimp(const ::aiMesh &mesh) override
Creates a mesh from assimp. @ownership Caller deletes unless documented otherwise.
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Definition Color.h:13
eve::BlendMode BlendMode
Compatibility alias for the shared 2D blend mode.
Definition BlendMode.h:8
static constexpr int kMaxLights
Definition Light.h:173
Backend-owned layout facts for a mesh uploaded through Graphics.
Definition Graphics.h:86
GpuMesh public API.
Definition Graphics.h:289
MeshVertex public API.
Definition Graphics.h:141