13#include <SDL2/SDL_vulkan.h>
31#include "common/config.h"
35#include "zeroerr/assert.h"
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>
54 if (!initialized)
throw Exception(
"newMeshFromAssimp: graphics not initialized");
55 if (
mesh.mNumVertices == 0 ||
mesh.mNumFaces == 0)
56 throw Exception(
"newMeshFromAssimp: empty mesh");
58 std::vector<MeshVertex>
verts;
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);
68 for (
unsigned i = 0; i <
mesh.mNumVertices; ++i) {
71 if (
mesh.HasNormals())
72 v.normal = {
mesh.mNormals[i].x,
mesh.mNormals[i].y,
mesh.mNormals[i].z};
74 v.normal = {0.f, 1.f, 0.f};
75 if (
mesh.HasTangentsAndBitangents()) {
77 const auto &bitangent =
mesh.mBitangents[i];
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);
82 if (
mesh.HasTextureCoords(0))
83 v.uv = {
mesh.mTextureCoords[0][i].x,
mesh.mTextureCoords[0][i].y};
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);
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]);
108 if (
indices.empty())
throw Exception(
"newMeshFromAssimp: no triangle faces");
110 std::unique_ptr<GpuMesh> gpu;
111 if (
mesh.mNumVertices <= 65535u) {
112 std::vector<uint16_t> idx16;
114 for (uint32_t i :
indices) idx16.push_back(uint16_t(i));
115 gpu = uploadGpuMesh16(
device, frameToken(),
verts, idx16);
119 auto handle = makeMeshHandle(*gpu);
123 if (
mesh.mNumAnimMeshes > 0) {
124 handle->initMorphBase(
int(
mesh.mNumVertices), basePos.data(), baseNrm.data(), baseUv.data());
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;
138 handle->addMorphTargetAbsolute(morphName, absPos.data());
142 assignMeshBounds(raw,
verts);
144 ownedGpuMeshes.push_back(std::move(gpu));
145 ownedMeshes.push_back(std::move(
handle));
151 if (!initialized)
throw Exception(
"newMeshFromAssimp: graphics not initialized");
152 if (
mesh.mNumVertices == 0 ||
mesh.mNumFaces == 0)
153 throw Exception(
"newMeshFromAssimp: empty mesh");
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();
170 aiMatrix3x3 nmat(worldTransform);
171 const float ndet = nmat.Determinant();
172 if (std::fabs(ndet) > 1e-8f) {
176 for (
unsigned i = 0; i <
mesh.mNumVertices; ++i) {
178 if (
mesh.HasNormals()) {
182 normals[i] = aiVector3D(0.f, 1.f, 0.f);
188 const bool flipWinding = worldTransform.Determinant() < 0.f;
189 std::vector<aiFace> flippedFaces;
190 std::vector<unsigned> flippedIdx;
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];
205 dst.mIndices = src.mIndices;
212 tmp.mPrimitiveTypes =
mesh.mPrimitiveTypes;
213 tmp.mNumVertices =
mesh.mNumVertices;
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];
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;
256 if (!initialized)
throw Exception(
"newMeshFromArrays: graphics not initialized");
259 if (
indexCount % 3 != 0)
throw Exception(
"newMeshFromArrays: indexCount must be multiple of 3");
264 v.
pos = {posXYZ[size_t(i) * 3u], posXYZ[size_t(i) * 3u + 1u], posXYZ[size_t(i) * 3u + 2u]};
266 v.normal = {nrmXYZ[size_t(i) * 3u], nrmXYZ[size_t(i) * 3u + 1u],
267 nrmXYZ[size_t(i) * 3u + 2u]};
269 v.normal = {0.f, 1.f, 0.f};
271 v.uv = {uvST[size_t(i) * 2u], uvST[size_t(i) * 2u + 1u]};
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]};
280 for (uint32_t
id :
idx) {
285 auto handle = makeMeshHandle(*gpu);
288 registerMeshRecord(gpu.get());
289 ownedGpuMeshes.push_back(std::move(gpu));
290 ownedMeshes.push_back(std::move(
handle));
295 if (!
mesh || !
mesh->gpuHandle)
return std::nullopt;
296 const auto *gpu =
static_cast<const GpuMesh *
>(
mesh->gpuHandle);
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;
302 static_cast<std::uint32_t
>(
sizeof(
MeshVertex)),
303 gpu->indexType == vk::IndexType::eUint16 ? 2u : 4u};
307 if (!
mesh || !
mesh->gpuHandle || !
mesh->hasMorphData() || !
mesh->isMorphDirty())
return false;
308 if (!initialized)
return false;
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);
317 std::vector<MeshVertex>
verts(
static_cast<size_t>(vc));
318 auto *gpu =
static_cast<GpuMesh *
>(
mesh->gpuHandle);
319 if (
mesh->hasGpuSkinning()) {
321 auto &
source = meshDrawVertices(*gpu);
322 void *mapped =
source.map();
323 if (!mapped)
return false;
327 const auto &
uv =
mesh->baseUv();
328 for (
int i = 0; i < vc; ++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]};
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]};
344 ensureDynamicRing(*gpu);
345 writeDynamicMesh(*gpu,
verts, getDevice(), frameToken(),
nullptr, 0);
346 mesh->markMorphClean();
353 if (!initialized || !
mesh || !
mesh->gpuHandle)
return false;
363 v.
pos = {posXYZ[size_t(i) * 3u], posXYZ[size_t(i) * 3u + 1u], posXYZ[size_t(i) * 3u + 2u]};
365 v.normal = {nrmXYZ[size_t(i) * 3u], nrmXYZ[size_t(i) * 3u + 1u],
366 nrmXYZ[size_t(i) * 3u + 2u]};
368 v.normal = {0.f, 1.f, 0.f};
370 v.uv = {uvST[size_t(i) * 2u], uvST[size_t(i) * 2u + 1u]};
375 auto *gpu =
static_cast<GpuMesh *
>(
mesh->gpuHandle);
379 ensureDynamicRing(*gpu);
381 mesh->gpuVertexCount = int(gpu->vertexCount);
382 mesh->indexCount = int(gpu->indexCount);
388 if (!initialized || !
mesh || !
mesh->gpuHandle || !joints4 || !weights4)
return false;
389 auto *gpu =
static_cast<GpuMesh *
>(
mesh->gpuHandle);
393 auto &
source = meshDrawVertices(*gpu);
394 void *mapped =
source.map();
395 if (!mapped)
return false;
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]);
403 glm::vec4(weights4[base], weights4[base + 1], weights4[base + 2], weights4[base + 3]);
405 ensureDynamicRing(*gpu);
406 writeDynamicMesh(*gpu,
verts, getDevice(), frameToken(),
nullptr, 0);
407 mesh->markGpuSkinned(
true);
412 if (!
mesh || !
mesh->gpuHandle)
return false;
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;
419 if (gpuIt == ownedGpuMeshes.end())
return false;
421 auto meshIt = std::find_if(ownedMeshes.begin(), ownedMeshes.end(),
422 [&](
const std::unique_ptr<Mesh> &
m) {
423 return m.get() == mesh;
425 if (meshIt == ownedMeshes.end())
return false;
428 waitForSharedGpuResources();
429 mesh->gpuHandle =
nullptr;
430 ownedGpuMeshes.erase(gpuIt);
432 (void)meshIt->release();
433 ownedMeshes.erase(meshIt);
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;
445 constexpr float kPi = 3.14159265358979323846f;
446 constexpr float kTwoPi = kPi * 2.f;
454 const int stride = slices + 1;
455 const int rows = stacks + 1;
456 std::vector<MeshVertex>
verts;
457 verts.reserve(
size_t(stride) *
size_t(
rows));
459 auto pushVert = [&](
float px,
float py,
float pz,
float u,
float v) {
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;
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);
481 pushVert(sinPhi * std::cos(theta), cosPhi, sinPhi * std::sin(theta),
u, fv);
486 indices.reserve(
size_t(slices) *
size_t(stacks) * 6u);
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);
507 auto handle = makeMeshHandle(*gpu);
509 assignMeshBounds(raw,
verts);
510 registerMeshRecord(gpu.get());
511 ownedGpuMeshes.push_back(std::move(gpu));
512 ownedMeshes.push_back(std::move(
handle));
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;
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;
529 const int stride = slices + 1;
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));
534 auto pushVert = [&](
float px,
float py,
float pz,
float nx,
float ny,
float nz,
float u,
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);
557 indices.reserve(
size_t(slices) *
size_t(stacks) * 6u +
558 size_t(
caps ? slices * 2 * 3 : 0));
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);
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,
590 for (
int x = 0;
x < slices; ++
x) {
592 indices.push_back(topRing + uint32_t(
x + 1));
593 indices.push_back(topRing + uint32_t(
x));
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,
608 for (
int x = 0;
x < slices; ++
x) {
610 indices.push_back(botRing + uint32_t(
x));
611 indices.push_back(botRing + uint32_t(
x + 1));
616 auto handle = makeMeshHandle(*gpu);
618 assignMeshBounds(raw,
verts);
619 registerMeshRecord(gpu.get());
620 ownedGpuMeshes.push_back(std::move(gpu));
621 ownedMeshes.push_back(std::move(
handle));
636 auto fail = [](
const char *
message) {
639 auto ownedShader = std::find_if(ownedGpuShaders.begin(), ownedGpuShaders.end(),
640 [&](
const auto &item) { return item->owner == &shader; });
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);
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");
659 }
catch (
const std::exception &
error) {
664void Graphics::drawMeshShaderRange(Mesh *
mesh,
const glm::mat4 &
model, Texture *texture,
const Color &
tint,
667 ASSERT(
mesh !=
nullptr);
668 if (!initialized)
throw Exception(
"drawMesh: graphics not initialized");
670 if (!swapchainPassOpen && !offscreen3DPassOpen)
671 throw Exception(
"drawMesh: call begin3DFrame first");
672 createMesh3DPipeline();
673 if (!mesh3dPipeline)
throw Exception(
"drawMesh: mesh3d pipeline missing");
677 throw Exception(
"drawMesh: shader is not a Mesh3D shader (use newMeshShader*)");
678 if (!
shader->gpuHandle)
throw Exception(
"drawMesh: shader has no GPU pipeline");
681 if (pbrSurface_ && !
shader) {
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);
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);
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);
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);
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;
719 gpuDecalAlb =
static_cast<GpuTexture *
>(decalFlatAlbedo->gpuHandle);
720 gpuDecalNrm =
static_cast<GpuTexture *
>(decalFlatNormal->gpuHandle);
721 gpuDecalPrm =
static_cast<GpuTexture *
>(decalFlatParams->gpuHandle);
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;
731 const bool useClustered = mesh3dClusteredActive && !
shader && mesh3dClusteredPipeline &&
733 auto &cb = currentPresentCb();
735 auto makeShadowUbo = [&]() {
736 ShadowUBO
s = mesh3dShadows.ubo;
737 if (!mesh3dShadows.active) {
741 s.bias.z = mesh3dShadowReceive ? 1.f : 0.f;
746 Mesh3DClusteredUBO ubo{};
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);
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));
761 mesh3dAlphaTechnique ==
"coverage")
763 ubo.texBomb = glm::vec4(mesh3dTexBombScale, mesh3dTexBombStrength, mesh3dTexBombRot,
766 glm::vec4(mesh3dParallaxScale, mesh3dParallaxMinLayers, mesh3dParallaxMaxLayers,
768 ubo.virtualTexture = mesh3dVirtualTexture;
769 ubo.virtualAtlas = mesh3dVirtualAtlas;
770 ubo.envProbeCenter = glm::vec4(mesh3dEnvProbeCenter, 1.f);
771 ubo.envProbeExtent = glm::vec4(mesh3dEnvProbeExtent, 0.f);
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)
778 ubo.reflectionProbeCenter[i] = glm::vec4(probe.center, probe.intensity);
779 ubo.reflectionProbeExtent[i] = glm::vec4(probe.extent, probe.blendDistance);
782 auto &cfslots = currentMesh3dClusteredFrameSlots();
783 if (cfslots.drawIndex >= cfslots.capacity) {
785 "[vulkan] clustered mesh3d UBO ring exhausted (%zu draws); draw skipped\n",
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};
801 if (mesh3dClusteredPipeline != lastMesh3dClusteredPipeline) {
802 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, mesh3dClusteredPipeline);
803 lastMesh3dClusteredPipeline = mesh3dClusteredPipeline;
805 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dClusteredPipelineLayout, 0, 1,
806 &set, 2, dynOffsets);
807 drawIndexedMesh(cb, *gpuMesh);
811 Mesh3DUBO ubo = mesh3dFrameUbo;
813 ubo.mvp = mesh3dFrameUbo.mvp *
model;
815 ubo.ambient = glm::vec4(glm::vec3(mesh3dLighting.ambient), mesh3dMetallic);
817 ubo.lightDir.w = float(lightCount);
818 ubo.cameraPos.w = mesh3dRoughness;
819 ubo.lightColor.w = envIntensity;
820 float surfaceCode = float(
int(mesh3dSurfaceMode));
825 ubo.texBomb = glm::vec4(mesh3dTexBombScale, mesh3dTexBombStrength, mesh3dTexBombRot,
828 glm::vec4(mesh3dParallaxScale, mesh3dParallaxMinLayers, mesh3dParallaxMaxLayers,
830 ubo.virtualTexture = mesh3dVirtualTexture;
831 ubo.virtualAtlas = mesh3dVirtualAtlas;
832 ubo.lodFade = mesh3dLodFade;
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)
839 ubo.reflectionProbeCenter[i] = glm::vec4(probe.center, probe.intensity);
840 ubo.reflectionProbeExtent[i] = glm::vec4(probe.extent, probe.blendDistance);
842 if (
mesh->hasGpuSkinning()) {
843 ubo.skinInfo.x =
static_cast<float>(
mesh->getSkinPaletteCount());
844 ubo.skinInfo.y =
static_cast<float>(mesh3dSkinInfluenceLimit);
846 for (
int i = 0; i < lightCount; ++i) ubo.lights[i] = mesh3dLighting.lights[i];
848 for (
int i = 0; i < lightCount; ++i) {
849 if (mesh3dLighting.lights[i].posRadius.w <= 0.f) {
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);
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);
865 auto &fslots = currentMesh3dFrameSlots();
866 if (fslots.drawIndex >= fslots.capacity) {
867 std::fprintf(stderr,
"[vulkan] mesh3d UBO ring exhausted (%zu draws); draw skipped\n",
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};
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) {
900 activePipeline = gs->mesh3dXrayPipeline;
902 if (!activePipeline)
return;
903 if (activePipeline != lastMesh3dPipeline) {
904 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, activePipeline);
905 lastMesh3dPipeline = activePipeline;
907 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, gs->pipelineLayout, 0, 1, &set, 2, dynOffsets);
909 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, gs->pipelineLayout, 1, 1, &gs->resourceSet, 0,
911 cb.pushConstants(gs->pipelineLayout, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0,
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;
928 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dPipelineLayout, 0, 1, &set, 2,
std::vector< std::uint32_t > verts
std::uint32_t vertexCount
std::uint32_t firstInstance
std::uint32_t instanceCount
std::vector< std::uint32_t > indices
std::vector< float > normals
std::vector< float > positions
const UnitySourceAsset & source
std::vector< double > phi
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.
EVENGINE_API_FOUNDATION public API.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
virtual bool releaseMesh(Mesh *mesh)
Eagerly releases a mesh created by this Graphics.
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.
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...
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.
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).
void computeBounds(const float *posXYZ, int vertexCount)
Compute the bounding sphere (centroid + max radius) from positions.
static constexpr uint32_t kPushConstantBytes
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
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...
eve::BlendMode BlendMode
Compatibility alias for the shared 2D blend mode.
static constexpr int kMaxLights
Backend-owned layout facts for a mesh uploaded through Graphics.
std::uint32_t vertexCount
static constexpr int kMaxProbes