Files
filament/tools/filamesh/src/MeshWriter.cpp
2022-09-15 17:23:54 -07:00

262 lines
11 KiB
C++

/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MeshWriter.h"
#include <filameshio/filamesh.h>
#include <meshoptimizer.h>
using namespace filamesh;
using namespace filament::math;
using namespace std;
template<typename T>
void write(ostream& out, const T& value) {
out.write((const char*) &value, sizeof(T));
}
template<typename T>
void write(ostream& out, const T* data, uint32_t count) {
out.write((const char*) data, sizeof(T) * count);
}
template<typename T>
size_t write(unsigned char* out, const vector<T>& data) {
memcpy(out, data.data(), data.size() * sizeof(T));
return data.size() * sizeof(T);
}
void MeshWriter::optimize(Mesh& mesh) {
// In debug builds, non-triangular data will assert in meshopt, but we need to have
// a safety check here anyway to prevent potential OOB reads in release builds.
if (mesh.indices.size() % 3 != 0) {
fprintf(stderr, "Mesh must be triangles.\n");
exit(1);
}
// First, re-order triangles to improve cache locality and reduce the number of VS invocations.
// Note that assimp already has aiProcess_ImproveCacheLocality, but MeshWriter doesn't know
// about assimp, and it doesn't hurt to do it again here since this generally runs offline.
meshopt_optimizeVertexCache(mesh.indices.data(), mesh.indices.data(), mesh.indices.size(),
mesh.vertexCount);
// At this point, triangle order has been established but we still need to shuffle vertices to
// optimize the fetch. This makes it so that lower-numbered indices generally come before
// higher-numbered indices.
if (mFlags & INTERLEAVED) {
meshopt_optimizeVertexFetch(mesh.vertices.data(), mesh.indices.data(),
mesh.indices.size(), mesh.vertices.data(), mesh.vertices.size(),
sizeof(Vertex));
} else {
const uint32_t vertexCount = mesh.vertexCount;
// Allocate a remapping table and create a copy of the index buffer.
vector<uint32_t> remappingVector(vertexCount);
vector<uint32_t> indicesVector = mesh.indices;
uint32_t* remapping = remappingVector.data();
const uint32_t* indices = indicesVector.data();
// Populate the remapping table.
meshopt_optimizeVertexFetchRemap(remapping, mesh.indices.data(),
mesh.indices.size(), vertexCount);
// Apply the remapping table.
meshopt_remapIndexBuffer(mesh.indices.data(), indices, mesh.indices.size(), remapping);
meshopt_remapVertexBuffer(mesh.positions.data(), mesh.positions.data(),
vertexCount, sizeof(decltype(Vertex::position)), remapping);
meshopt_remapVertexBuffer(mesh.tangents.data(), mesh.tangents.data(),
vertexCount, sizeof(decltype(Vertex::tangents)), remapping);
meshopt_remapVertexBuffer(mesh.colors.data(), mesh.colors.data(),
vertexCount, sizeof(decltype(Vertex::color)), remapping);
meshopt_remapVertexBuffer(mesh.uv0.data(), mesh.uv0.data(),
vertexCount, sizeof(decltype(Vertex::uv0)), remapping);
if (!mesh.uv1.empty()) {
meshopt_remapVertexBuffer(mesh.uv1.data(), mesh.uv1.data(),
vertexCount, sizeof(decltype(Vertex::uv0)), remapping);
}
}
// As a last step, the meshoptimizer README recommends applying individual meshopt_quantize*
// functions as needed, but we actually already quantized the data according to our constraints
// e.g. we already (potentially) use snorm16 for uvs, half-floats for tangents, etc.
}
bool MeshWriter::serialize(ostream& out, Mesh& mesh) {
const bool hasIndex16 = mesh.vertexCount <= numeric_limits<uint16_t>::max();
const bool hasUV1 = !mesh.uv1.empty();
const size_t vertexSize = sizeof(Vertex) + (hasUV1 ? sizeof(ushort2) : 0);
if ((mFlags & INTERLEAVED) && hasUV1) {
cerr << "Interleaved vertices can only have 1 UV set." << endl;
return false;
}
// Compute the overall bounding box.
Box aabb = mesh.parts.at(0).aabb;
for (size_t i = 1; i < mesh.parts.size(); i++) {
aabb.unionSelf(mesh.parts.at(i).aabb);
}
// It's safe to optimize the mesh regardless of the compression setting.
optimize(mesh);
// Perform compression of vertex data if it has been requested.
CompressionHeader cheader {};
vector<unsigned char> compressedVertices;
if (mFlags & COMPRESSION) {
compressedVertices.resize(meshopt_encodeVertexBufferBound(mesh.vertexCount, vertexSize));
size_t compressedVertexSize;
if (mFlags & INTERLEAVED) {
compressedVertexSize = meshopt_encodeVertexBuffer(compressedVertices.data(),
compressedVertices.size(), mesh.vertices.data(), mesh.vertexCount, vertexSize);
} else {
unsigned char* cptr = compressedVertices.data();
unsigned char* cend = compressedVertices.data() + compressedVertices.size();
cheader.positions = meshopt_encodeVertexBuffer(cptr, cend - cptr, mesh.positions.data(),
mesh.vertexCount, sizeof(decltype(Vertex::position)));
cptr += cheader.positions;
cheader.tangents = meshopt_encodeVertexBuffer(cptr, cend - cptr, mesh.tangents.data(),
mesh.vertexCount, sizeof(decltype(Vertex::tangents)));
cptr += cheader.tangents;
cheader.colors = meshopt_encodeVertexBuffer(cptr, cend - cptr, mesh.colors.data(),
mesh.vertexCount, sizeof(decltype(Vertex::color)));
cptr += cheader.colors;
cheader.uv0 = meshopt_encodeVertexBuffer(cptr, cend - cptr, mesh.uv0.data(),
mesh.vertexCount, sizeof(decltype(Vertex::uv0)));
cptr += cheader.uv0;
if (hasUV1) {
cheader.uv1 = meshopt_encodeVertexBuffer(cptr, cend - cptr, mesh.uv1.data(),
mesh.vertexCount, sizeof(decltype(Vertex::uv0)));
cptr += cheader.uv1;
}
assert(cend - cptr >= 0);
compressedVertexSize = cptr - compressedVertices.data();
}
if (compressedVertexSize == 0) {
cerr << "Unable to compress vertex buffer." << endl;
return false;
}
compressedVertices.resize(compressedVertexSize);
}
// Perform compression of index data if it has been requested.
vector<unsigned char> compressedIndices;
if (mFlags & COMPRESSION) {
compressedIndices.resize(meshopt_encodeIndexBufferBound(mesh.indices.size(),
mesh.vertexCount));
size_t result = meshopt_encodeIndexBuffer(compressedIndices.data(),
compressedIndices.size(), mesh.indices.data(), mesh.indices.size());
if (result == 0) {
cerr << "Unable to compress index buffer." << endl;
return false;
}
compressedIndices.resize(result);
}
write(out, "FILAMESH", 8 * sizeof(char));
Header header;
header.version = VERSION;
header.parts = uint32_t(mesh.parts.size());
header.aabb = aabb;
header.flags = mFlags;
if (mFlags & INTERLEAVED) {
header.offsetPosition = offsetof(Vertex, position);
header.offsetTangents = offsetof(Vertex, tangents);
header.offsetColor = offsetof(Vertex, color);
header.offsetUV0 = offsetof(Vertex, uv0);
header.offsetUV1 = numeric_limits<uint32_t>::max();
header.stridePosition = sizeof(Vertex);
header.strideTangents = sizeof(Vertex);
header.strideColor = sizeof(Vertex);
header.strideUV0 = sizeof(Vertex);
header.strideUV1 = numeric_limits<uint32_t>::max();
} else {
header.offsetPosition = 0;
header.offsetTangents = mesh.vertexCount * sizeof(Vertex::position);
header.offsetColor = header.offsetTangents + mesh.vertexCount * sizeof(Vertex::tangents);
header.offsetUV0 = header.offsetColor + mesh.vertexCount * sizeof(Vertex::color);
header.offsetUV1 = numeric_limits<uint32_t>::max();
header.stridePosition = 0;
header.strideTangents = 0;
header.strideColor = 0;
header.strideUV0 = 0;
header.strideUV1 = numeric_limits<uint32_t>::max();
if (hasUV1) {
header.offsetUV1 = header.offsetUV0 + mesh.vertexCount * sizeof(Vertex::uv0);
header.strideUV1 = 0;
}
}
header.vertexCount = mesh.vertexCount;
header.indexType = uint32_t(hasIndex16 ? UI16 : UI32);
header.indexCount = mesh.indices.size();
if (mFlags & COMPRESSION) {
header.vertexSize = sizeof(cheader) + compressedVertices.size();
header.indexSize = compressedIndices.size();
} else {
header.vertexSize = mesh.vertexCount * vertexSize;
header.indexSize = mesh.indices.size() * (hasIndex16 ? sizeof(uint16_t) : sizeof(uint32_t));
}
write(out, header);
if (mFlags & COMPRESSION) {
write(out, &cheader, 1);
write(out, compressedVertices.data(), compressedVertices.size());
} else if (mFlags & INTERLEAVED) {
write(out, mesh.vertices.data(), uint32_t(mesh.vertices.size()));
} else {
write(out, mesh.positions.data(), uint32_t(mesh.positions.size()));
write(out, mesh.tangents.data(), uint32_t(mesh.tangents.size()));
write(out, mesh.colors.data(), uint32_t(mesh.colors.size()));
write(out, mesh.uv0.data(), uint32_t(mesh.uv0.size()));
if (hasUV1) {
write(out, mesh.uv1.data(), uint32_t(mesh.uv1.size()));
}
}
if (mFlags & COMPRESSION) {
write(out, compressedIndices.data(), compressedIndices.size());
} else if (!hasIndex16) {
write(out, mesh.indices.data(), uint32_t(mesh.indices.size()));
} else {
vector<uint16_t> smallIndices;
smallIndices.resize(mesh.indices.size());
for (size_t i = 0; i < mesh.indices.size(); i++) {
smallIndices[i] = static_cast<uint16_t>(mesh.indices[i]);
}
write(out, smallIndices.data(), uint32_t(smallIndices.size()));
}
write(out, mesh.parts.data(), header.parts);
write(out, uint32_t(mesh.materials.size()));
for (const auto& name : mesh.materials) {
write(out, uint32_t(name.size()));
write(out, name.c_str(), uint32_t(name.size()));
write(out, char(0));
}
return true;
}