Compare commits

..

1 Commits

Author SHA1 Message Date
Josh Pearson
986921cc9c Preserve low res textures
Any texture less than 64x64 keep original size.  Improves building LOD texture quality, and doesn't blow VRAM budget
2025-01-09 14:07:43 -07:00
2 changed files with 72 additions and 351 deletions

24
.vscode/launch.json vendored
View File

@@ -24,30 +24,6 @@
}
]
},
{
"name": "dca3-sim (linux)",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/dreamcast/dca3-sim.elf",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/dreamcast",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"description": "Set Disassembly Flavor to Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
}
]
},
{
"name": "dca3-sim (mac)",
"type": "cppdbg",

View File

@@ -4,11 +4,6 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <cstdint>
#include <vector>
#include <iostream>
#include <cmath>
#include <set>
#if !defined(DC_TEXCONV) && !defined(MACOS64)
#include <malloc.h>
@@ -600,8 +595,6 @@ struct atomic_context_t {
matrix_t worldView, mtx;
UniformObject uniform;
V3d cameraDir;
float cosPhi;
};
/* END Ligting Structs and Defines */
@@ -1761,23 +1754,24 @@ void addInterpolatedVertex(const pvr_vertex16_t& v1, const pvr_vertex16_t& v2, u
}
struct MeshInfo {
uint32_t meshletCount;
uint32_t meshletOffset;
int16_t meshletCount;
int16_t meshletOffset;
};
static_assert(sizeof(MeshInfo) == 8);
static_assert(sizeof(MeshInfo) == 4);
struct MeshletInfo {
RwSphere boundingSphere;
uint8_t flags;
uint8_t clusterCount;
uint16_t flags;
int8_t pad;
int8_t vertexSize;
uint8_t vertexCount;
uint16_t vertexCount;
uint16_t indexCount;
uint32_t vertexOffset;
uint32_t indexOffset;
uint32_t skinIndexOffset;
uint32_t skinWeightOffset;
};
static_assert(sizeof(MeshletInfo) == 36); // or 28 if !skin
static_assert(sizeof(MeshletInfo) == 40); // or 32 if !skin
inline __attribute__((always_inline)) void setLights(Atomic *atomic, WorldLights *lightData, UniformObject &uniformObject)
@@ -3235,41 +3229,6 @@ size_t vertexBufferFree() {
}
float calculateMaxAngularOffset(
const V3d& cameraPos, // Camera position
const V3d& cameraLookAt, // Camera look-at point (not direction)
const V3d& sphereCenter, // Sphere center
float sphereRadius) // Sphere radius
{
// Compute the look-at direction vector (normalized)
V3d frustumAxis = cameraLookAt;
// Vector from camera to sphere center
V3d V = sub(sphereCenter, cameraPos);
if (length(V) < sphereRadius) {
return 1;
}
// Distance along the camera look-at direction
float d_parallel = dot(V, frustumAxis);
// Perpendicular vector (V_perp = V - d_parallel * frustumAxis)
V3d V_perp = {
V.x - d_parallel * frustumAxis.x,
V.y - d_parallel * frustumAxis.y,
V.z - d_parallel * frustumAxis.z
};
float d_perpendicular = length(V_perp);
// Total perpendicular distance including sphere radius
float d_total = d_perpendicular + sphereRadius;
// Maximum angular offset
float theta_max = std::atan2(d_total, d_parallel);
return theta_max; // Result in radians
}
void defaultRenderCB(ObjPipeline *pipe, Atomic *atomic) {
rw::Camera *cam = engine->currentCamera;
// Frustum Culling
@@ -3327,28 +3286,6 @@ void defaultRenderCB(ObjPipeline *pipe, Atomic *atomic) {
mat_apply((matrix_t*)&atomicContexts.back().worldView);
mat_store((matrix_t*)&atomicContexts.back().mtx);
// TODO: Don't invert twice for the same atomic
Matrix magic;
Matrix::invert(&magic, atomic->getFrame()->getLTM());
V3d camera_dir;
V3d::transformVectors(&camera_dir, &cam->getFrame()->getLTM()->at, 1, &magic);
camera_dir = normalize(camera_dir);
// required because of camera up being negative
camera_dir.x*=-1;
camera_dir.y*=-1;
camera_dir.z*=-1;
atomicContexts.back().cameraDir = camera_dir;
float angle = calculateMaxAngularOffset(cam->getFrame()->getLTM()->pos, cam->getFrame()->getLTM()->at, atomic->getWorldBoundingSphere()->center, atomic->getWorldBoundingSphere()->radius);
angle = fabs(angle);
if (angle > 70/2 * M_PI / 180) {
angle = 70/2 * M_PI / 180;
}
atomicContexts.back().cosPhi = cosf(90 * M_PI / 180 + 5.1 * M_PI / 180 + angle);
int16_t contextId = atomicContexts.size() - 1;
assert(numMeshes <= 32767);
@@ -3678,37 +3615,9 @@ void defaultRenderCB(ObjPipeline *pipe, Atomic *atomic) {
auto indexData = (int8_t*)&dcModel->data[meshlet->indexOffset];
if (!clippingRequired) {
unsigned numClusters = meshlet->clusterCount;
auto currentIndexData = indexData;
do {
V3d coneNormal = { currentIndexData[0] / 127.f, currentIndexData[1] / 127.f, currentIndexData[2] / 127.f };
float costheta = dot(acp->cameraDir, coneNormal);
unsigned indexCount = (uint8_t&)currentIndexData[3];
currentIndexData += 4;
if (costheta >= acp->cosPhi) {
submitMeshletSelector[textured](OCR_SPACE, currentIndexData, indexCount);
} else {
// printf("CONE CULL, %f %f, %f %f %f\n", costheta, acp->cosPhi, meshlet->coneNormal.x, meshlet->coneNormal.y, meshlet->coneNormal.z);
}
currentIndexData += indexCount;
} while(--numClusters != 0);
submitMeshletSelector[textured](OCR_SPACE, indexData, meshlet->indexCount);
} else {
unsigned numClusters = meshlet->clusterCount;
auto currentIndexData = indexData;
do {
V3d coneNormal = { currentIndexData[0] / 127.f, currentIndexData[1] / 127.f, currentIndexData[2] / 127.f };
float costheta = dot(acp->cameraDir, coneNormal);
unsigned indexCount = (uint8_t&)currentIndexData[3];
currentIndexData += 4;
if (costheta >= acp->cosPhi) {
clipAndsubmitMeshletSelector[textured](OCR_SPACE, currentIndexData, indexCount);
} else {
// printf("CONE CULL, %f %f, %f %f %f\n", costheta, acp->cosPhi, meshlet->coneNormal.x, meshlet->coneNormal.y, meshlet->coneNormal.z);
}
currentIndexData += indexCount;
} while(--numClusters != 0);
clipAndsubmitMeshletSelector[textured](OCR_SPACE, indexData, meshlet->indexCount);
}
if (meshContext->matfxContextOffset != SIZE_MAX) {
@@ -3732,37 +3641,9 @@ void defaultRenderCB(ObjPipeline *pipe, Atomic *atomic) {
tnlMeshletEnvMap(OCR_SPACE, &dcModel->data[meshlet->vertexOffset] + normalOffset, meshlet->vertexCount, meshlet->vertexSize, &matfxContext->mtx, matfxContext->coefficient);
if (!clippingRequired) {
unsigned numClusters = meshlet->clusterCount;
auto currentIndexData = indexData;
do {
V3d coneNormal = { currentIndexData[0] / 127.f, currentIndexData[1] / 127.f, currentIndexData[2] / 127.f };
float costheta = dot(acp->cameraDir, coneNormal);
unsigned indexCount = (uint8_t&)currentIndexData[3];
currentIndexData += 4;
if (costheta >= acp->cosPhi) {
submitMeshletSelector[true](OCR_SPACE, currentIndexData, indexCount);
} else {
// printf("CONE CULL, %f %f, %f %f %f\n", costheta, acp->cosPhi, meshlet->coneNormal.x, meshlet->coneNormal.y, meshlet->coneNormal.z);
}
currentIndexData += indexCount;
} while(--numClusters != 0);
submitMeshletSelector[true](OCR_SPACE, indexData, meshlet->indexCount);
} else {
unsigned numClusters = meshlet->clusterCount;
auto currentIndexData = indexData;
do {
V3d coneNormal = { currentIndexData[0] / 127.f, currentIndexData[1] / 127.f, currentIndexData[2] / 127.f };
float costheta = dot(acp->cameraDir, coneNormal);
unsigned indexCount = (uint8_t&)currentIndexData[3];
currentIndexData += 4;
if (costheta >= acp->cosPhi) {
clipAndsubmitMeshletSelector[true](OCR_SPACE, currentIndexData, indexCount);
} else {
// printf("CONE CULL, %f %f, %f %f %f\n", costheta, acp->cosPhi, meshlet->coneNormal.x, meshlet->coneNormal.y, meshlet->coneNormal.z);
}
currentIndexData += indexCount;
} while(--numClusters != 0);
clipAndsubmitMeshletSelector[true](OCR_SPACE, indexData, meshlet->indexCount);
}
}
}
@@ -4006,19 +3887,19 @@ imageFindRasterFormat(Image *img, int32 type,
;
if(downsampleMode >= HALF) {
if(height / 2 >= 16) {
if(height / 2 >= 64) {
height /= 2;
}
if(width / 2 >= 16) {
if(width / 2 >= 64) {
width /= 2;
}
}
if(downsampleMode >= QUARTER) {
if(height / 2 >= 16) {
if(height / 2 >= 32) {
height /= 2;
}
if(width / 2 >= 16) {
if(width / 2 >= 32) {
width /= 2;
}
}
@@ -4901,84 +4782,17 @@ centerTexCoords(Geometry *g)
rwFree(groupIDs);
}
// Cluster structure
struct ConeCluster {
V3d normal; // Average normal of the cluster
std::vector<uint32_t> faces; // Indices of triangles in this cluster
};
V3d calculateNormal(const V3d& v1, const V3d& v2, const V3d& v3) {
bool isDegenerate(const V3d& v1, const V3d& v2, const V3d& v3) {
V3d u = {v2.x - v1.x, v2.y - v1.y, v2.z - v1.z};
V3d v = {v3.x - v1.x, v3.y - v1.y, v3.z - v1.z};
V3d crs = cross(u, v);
if (length(crs) < 0.00001f) {
return {0, 0, 0};
if (length(crs) < 0.0000001f) {
return true;
} else {
return normalize(crs);
return false;
}
}
// Process mesh with cone clustering logic
std::vector<ConeCluster> processMeshWithClustering(uint16_t* indices, uint32_t numIndices, const V3d* vertices, float cosThreshold) {
std::vector<V3d> triangleNormals(numIndices / 3); // Store triangle normals
std::vector<bool> processed(numIndices / 3, false); // Track processed triangles
// Compute normals for each triangle
for (uint32_t i = 0; i < numIndices; i += 3) {
uint16_t idx1 = indices[i];
uint16_t idx2 = indices[i + 1];
uint16_t idx3 = indices[i + 2];
// Calculate and store the triangle normal
triangleNormals[i / 3] = calculateNormal(vertices[idx1], vertices[idx2], vertices[idx3]);
}
// Perform clustering
std::vector<ConeCluster> clusters;
for (size_t i = 0; i < triangleNormals.size(); ++i) {
if (processed[i]) continue;
if (length(triangleNormals[i]) < 0.000001f)
continue;
// Create a new cluster
ConeCluster cluster;
cluster.normal = triangleNormals[i];
cluster.faces.push_back(i);
processed[i] = true;
// Find and add similar triangles to this cluster
for (size_t j = 0; j < triangleNormals.size(); ++j) {
if (processed[j]) continue;
float similarity = dot(cluster.normal, triangleNormals[j]);
if (similarity >= cosThreshold) {
cluster.faces.push_back(j);
processed[j] = true;
}
}
// Normalize the cluster normal (optional)
cluster.normal = normalize(cluster.normal);
// Store the cluster
clusters.push_back(cluster);
}
// // Process each cluster
// for (size_t c = 0; c < clusters.size(); ++c) {
// std::cout << "Cluster " << c << ":\n";
// std::cout << " Average Normal: (" << clusters[c].normal.x << ", " << clusters[c].normal.y << ", " << clusters[c].normal.z << ")\n";
// std::cout << " Triangles: ";
// for (auto face : clusters[c].faces) {
// std::cout << face << " ";
// }
// std::cout << "\n";
// }
return clusters;
}
bool isDegenerateByIndex(uint16_t idx1, uint16_t idx2, uint16_t idx3) {
return idx1 == idx2 || idx1 == idx3 || idx2 == idx3;
}
@@ -5149,16 +4963,10 @@ RwSphere calculateBoundingSphere(V3d* vertexData, size_t count) {
return sphere;
}
struct ConeClusterStrip {
triangle_stripper::primitive_group* strip;
ConeCluster* cluster;
};
struct meshlet {
std::set<uint16_t> vertices;
std::map<uint16_t, uint8_t> vertexToLocalIndex;
std::vector<ConeClusterStrip*> strips;
std::vector<triangle_stripper::primitive_group*> strips;
size_t vertexDataOffset;
size_t indexDataOffset;
size_t skinIndexDataOffset;
@@ -5216,8 +5024,7 @@ void processGeom(Geometry *geo) {
int32 n = geo->meshHeader->numMeshes;
auto meshes = geo->meshHeader->getMeshes();
std::vector<std::vector<primitive_vector>> pvecs(n);
std::vector<std::vector<ConeCluster>> pclus(n);
std::vector<primitive_vector> pvecs(n);
std::vector<std::vector<meshlet>> meshMeshlets(n);
size_t totalIndices = 0, strips = 0, totalTrilist = 0;
@@ -5243,10 +5050,6 @@ void processGeom(Geometry *geo) {
skinIndices = (uint32_t*)skin->indices;
}
float angularThreshold = 5.0f * M_PI / 180.0f;
float cosThreshold = std::cos(angularThreshold);
std::vector<size_t> canonicalIdx(geo->numVertices, SIZE_MAX);
for (size_t i = 0; i < geo->numVertices; i++) {
@@ -5294,7 +5097,6 @@ void processGeom(Geometry *geo) {
}
}
texconvf("Found %zu vertex duplicates, %.2f%%\n", dups, (float)dups/geo->numVertices*100);
for (int meshNum = 0; meshNum < n; meshNum++) {
auto mesh = &meshes[meshNum];
@@ -5330,44 +5132,28 @@ void processGeom(Geometry *geo) {
mesh->indices[i] = canonicalIdx[mesh->indices[i]];
}
auto clusters = processMeshWithClustering(mesh->indices, mesh->numIndices, geo->morphTargets[0].vertices, cosThreshold);
for (auto&& cluster: clusters) {
std::vector<uint16_t> idx;
idx.reserve(cluster.faces.size()*3);
for (auto && face: cluster.faces) {
if (isDegenerateByIndex(mesh->indices[face*3 + 0], mesh->indices[face*3 + 1], mesh->indices[face*3 + 2])) {
continue;
}
idx.push_back(mesh->indices[face*3 + 0]);
idx.push_back(mesh->indices[face*3 + 1]);
idx.push_back(mesh->indices[face*3 + 2]);
}
indices Indices(idx.begin(), idx.end());
primitive_vector PrimitivesVector;
{
indices Indices(mesh->indices, mesh->indices + mesh->numIndices);
tri_stripper TriStripper(Indices);
TriStripper.SetMinStripSize(0);
TriStripper.SetCacheSize(0);
TriStripper.SetBackwardSearch(true);
TriStripper.Strip(&PrimitivesVector);
pvecs[meshNum].push_back(PrimitivesVector);
TriStripper.Strip(&pvecs[meshNum]);
}
pclus[meshNum] = clusters;
mesh->indices = oldIndices;
mesh->numIndices = oldNumIndices;
for (auto &&strip2: pvecs[meshNum]) {
for (auto &&strip: strip2) {
totalIndices += strip.Indices.size();
if (strip.Type == TRIANGLES) {
assert(strip.Indices.size()%3==0);
strips += strip.Indices.size()/3;
} else {
strips ++;
}
for (auto &&strip: pvecs[meshNum]) {
totalIndices += strip.Indices.size();
if (strip.Type == TRIANGLES) {
assert(strip.Indices.size()%3==0);
strips += strip.Indices.size()/3;
} else {
strips ++;
}
}
}
@@ -5380,34 +5166,23 @@ void processGeom(Geometry *geo) {
size_t meshVerticesCount = 0;
size_t meshletIndexesCount = 0;
size_t meshletVerticesCount = 0;
std::vector<std::vector<ConeClusterStrip>> meshConeClusterStrips(pvecs.size());
for (int pvn = 0; pvn < pvecs.size(); pvn++) {
auto &&prims2 = pvecs[pvn];
auto &&prims = pvecs[pvn];
std::set<uint16_t> meshletVertices;
std::list<ConeClusterStrip*> strips;
std::vector<ConeClusterStrip*> meshletStrips;
std::vector<primitive_group*> meshletStrips;
for (int cvn = 0; cvn < prims2.size(); cvn++) {
auto&& prims = prims2[cvn];
std::list<ConeClusterStrip> strips;
for (auto &&strip: prims) {
meshConeClusterStrips[pvn].push_back({&strip, &pclus[pvn][cvn]});
}
}
for (auto&& ccs: meshConeClusterStrips[pvn]) {
strips.push_back(&ccs);
std::list<primitive_group*> strips;
for (auto &&strip: prims) {
strips.push_back(&strip);
}
#undef printf
while(strips.size()) {
for(;;) {
// pluck strip with fewest new indices
ConeClusterStrip* bestStrip = nullptr;
primitive_group* bestStrip = nullptr;
size_t remainingVertices = 128 - meshletVertices.size();
size_t bestSharedVertices = 0;
@@ -5416,7 +5191,7 @@ void processGeom(Geometry *geo) {
auto &&strip = *strip_ptr;
std::set<uint16_t> newVertices;
size_t sharedVertices = 0;
for (auto &&idx: strip.strip->Indices) {
for (auto &&idx: strip.Indices) {
if (meshletVertices.find(idx) == meshletVertices.end()) {
newVertices.insert(idx);
} else {
@@ -5439,7 +5214,7 @@ void processGeom(Geometry *geo) {
// add strip to meshlet
meshletStrips.push_back(bestStrip);
for (auto &&idx: bestStrip->strip->Indices) {
for (auto &&idx: bestStrip->Indices) {
meshletVertices.insert(idx);
}
strips.remove(bestStrip);
@@ -5449,7 +5224,7 @@ void processGeom(Geometry *geo) {
// printf("Meshlet constructed, %ld strips, %zu vertices\n", meshletStrips.size(), meshletVertices.size());
for (auto &&strip: meshletStrips) {
meshletIndexesCount += strip->strip->Indices.size();
meshletIndexesCount += strip->Indices.size();
}
meshletVerticesCount += meshletVertices.size();
@@ -5467,13 +5242,10 @@ void processGeom(Geometry *geo) {
}
std::set<uint16_t> meshVertices;
for (int cvn = 0; cvn < prims2.size(); cvn++) {
auto&& prims = prims2[cvn];
for (auto &&strip: prims) {
meshIndexesCount += strip.Indices.size();
for (auto &&idx: strip.Indices) {
meshVertices.insert(idx);
}
for (auto &&strip: prims) {
meshIndexesCount += strip.Indices.size();
for (auto &&idx: strip.Indices) {
meshVertices.insert(idx);
}
}
meshVerticesCount += meshVertices.size();
@@ -5490,15 +5262,19 @@ void processGeom(Geometry *geo) {
for (size_t i = 0; i < meshMeshlets.size(); i++) {
auto &&mesh = meshMeshlets[i];
assert(mesh.size() <= UINT32_MAX);
meshData.write<uint32_t>(mesh.size());
assert(mesh.size() <= 32767);
meshData.write<int16_t>(mesh.size());
assert((meshletData.size() + meshMeshlets.size() * sizeof(MeshInfo)) <= UINT32_MAX);
meshData.write<uint32_t>(meshletData.size() + meshMeshlets.size() * sizeof(MeshInfo));
assert((meshletData.size() + meshMeshlets.size() * 4) <= 32767);
meshData.write<int16_t>(meshletData.size() + meshMeshlets.size() * 4);
for (auto && meshlet: mesh) {
auto boundingSphere = meshlet.calculateBoundingSphere(vertices);
uint32_t totalIndexes = 0;
for(auto&& strip: meshlet.strips) {
totalIndexes += strip->Indices.size();
}
// write out vertex data
@@ -5521,50 +5297,18 @@ void processGeom(Geometry *geo) {
// write out index data
meshlet.indexDataOffset = indexData.size();
std::map<ConeCluster*, std::vector<triangle_stripper::primitive_group*>> groupedStrips;
for(auto&& clusterStrip: meshlet.strips) {
groupedStrips[clusterStrip->cluster].push_back(clusterStrip->strip);
}
size_t clusterCount = 0;
for(auto&& stripGroup: groupedStrips) {
uint32_t totalIndexes = 256; // force a new cluster
size_t totalIndexPatchPoint = 0;
for (auto&& strip: stripGroup.second) {
if (totalIndexes + strip->Indices.size() > 255) {
if (totalIndexPatchPoint) {
assert(totalIndexes != 0);
assert(totalIndexes <= 255);
indexData[totalIndexPatchPoint] = totalIndexes;
}
totalIndexes = 0;
clusterCount++;
indexData.write<int8_t>(stripGroup.first->normal.x * 127);
indexData.write<int8_t>(stripGroup.first->normal.y * 127);
indexData.write<int8_t>(stripGroup.first->normal.z * 127);
totalIndexPatchPoint = indexData.size();
indexData.write<uint8_t>(0);
for(auto&& strip: meshlet.strips) {
if (strip->Type == TRIANGLES) {
for (size_t i = 0; i < strip->Indices.size(); i+=3) {
indexData.write<uint8_t>(meshlet.vertexToLocalIndex[strip->Indices[i]]);
indexData.write<uint8_t>(meshlet.vertexToLocalIndex[strip->Indices[i+1]]);
indexData.write<uint8_t>(meshlet.vertexToLocalIndex[strip->Indices[i+2]] | 128);
}
totalIndexes += strip->Indices.size();
if (strip->Type == TRIANGLES) {
for (size_t i = 0; i < strip->Indices.size(); i+=3) {
indexData.write<uint8_t>(meshlet.vertexToLocalIndex[strip->Indices[i]]);
indexData.write<uint8_t>(meshlet.vertexToLocalIndex[strip->Indices[i+1]]);
indexData.write<uint8_t>(meshlet.vertexToLocalIndex[strip->Indices[i+2]] | 128);
}
} else {
for (size_t i = 0; i < strip->Indices.size(); i++) {
indexData.write<uint8_t>(meshlet.vertexToLocalIndex[strip->Indices[i]] | ((i + 1) == strip->Indices.size() ? 128 : 0));
}
} else {
for (size_t i = 0; i < strip->Indices.size(); i++) {
indexData.write<uint8_t>(meshlet.vertexToLocalIndex[strip->Indices[i]] | ((i + 1) == strip->Indices.size() ? 128 : 0));
}
}
assert(totalIndexPatchPoint != 0);
assert(totalIndexes != 0);
assert(totalIndexes <= 255);
indexData[totalIndexPatchPoint] = totalIndexes;
}
// write out skinning data
@@ -5715,14 +5459,15 @@ void processGeom(Geometry *geo) {
// write out meshlet data
meshletData.write(boundingSphere);
//isTextured, isNormaled, isColored, small_xyz, pad_xyz, small_uv
uint8_t flags = texcoorded | (normaled << 1) | (colored << 2) | (!big_vertex << 3) | (pad_xyz << 4) | (!big_uv << 5);
meshletData.write<uint8_t>(flags);
assert(clusterCount <= 255);
meshletData.write<uint8_t>(clusterCount);
meshletData.write<uint8_t>(vertexSize);
uint16_t flags = texcoorded | (normaled << 1) | (colored << 2) | (!big_vertex << 3) | (pad_xyz << 4) | (!big_uv << 5);
meshletData.write<uint16_t>(flags);
meshletData.write<uint8_t>(0);
//bool textured, bool normaled, bool colored, bool big_vertex, bool big_uv, bool pad_xyz
assert(meshlet.vertices.size() <= 255);
meshletData.write<uint8_t>(meshlet.vertices.size());
meshletData.write<uint8_t>(vertexSize);
assert(meshlet.vertices.size() <= 65535);
meshletData.write<uint16_t>(meshlet.vertices.size());
assert(totalIndexes <= 65535);
meshletData.write<uint16_t>(totalIndexes);
meshlet.rewriteOffsetVDO = meshletData.size();
meshletData.write<uint32_t>(meshlet.vertexDataOffset); // will be patched
meshlet.rewriteOffsetIDO = meshletData.size();