Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mesh compression #129

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

#include <Corrade/Containers/Iterable.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/Reference.h>
#include <Corrade/Utility/ConfigurationGroup.h>
#include <Magnum/Math/PackingBatch.h>
#include <Magnum/Math/Vector3.h>
Expand All @@ -38,7 +39,7 @@
#include <Magnum/Trade/ArrayAllocator.h>
#include <Magnum/Trade/MeshData.h>
#include <meshoptimizer.h>

#include <Corrade/Containers/StringView.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit-pick: there should be a space below this line

namespace Magnum { namespace Trade {

MeshOptimizerSceneConverter::MeshOptimizerSceneConverter(PluginManager::AbstractManager& manager, const Containers::StringView& plugin): AbstractSceneConverter{manager, plugin} {}
Expand Down Expand Up @@ -223,6 +224,36 @@ bool convertInPlaceInternal(const char* prefix, MeshData& mesh, const SceneConve
} else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}

if((configuration.value<bool>("encodeVertex") || configuration.value<bool>("decodeVertex")) && (mesh.vertexData().size() > 256)) {
Error{} << prefix << "Compression and decompression is not possible with vertex data bigger than 256 bytes";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't sound quite right, but maybe I just don't fully understand how the mesh compression works in magnum:

I would suppose that even compressed meshes can get quite a lot bigger than just 256 bytes, maybe you meant that mesh compression is not possible with "more" than 256 bytes? 🤔 Feel free to point out in case I'm missing something.

return false;
}

if(configuration.value<bool>("encodeVertex")) {
unsigned int encoded_attribute_count = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
unsigned int encoded_attribute_count = 0;
UnsignedInt encodedAttributeCount = 0;

To adhere to corrade coding style :)

for(UnsignedInt i = 0; i < mesh.attributeCount(); ++i) {
if(isVertexFormatImplementationSpecific(mesh.attributeFormat(i))) encoded_attribute_count++;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if(isVertexFormatImplementationSpecific(mesh.attributeFormat(i))) encoded_attribute_count++;
if(isVertexFormatImplementationSpecific(mesh.attributeFormat(i))) ++encoded_attribute_count;

nit-pick: it doesn't make a difference, but x++ stands out to me to be there for some specific reason. (@mosra might think differently)

}
if(encoded_attribute_count == mesh.attributeCount()) {
Error{} << prefix << "Compression is not possible when all attributes are in implementation-specific format";
return false;
}
}

if(configuration.value<bool>("decodeVertex")) {
for(UnsignedInt i = 0; i < mesh.attributeCount(); ++i) {
if(!isVertexFormatImplementationSpecific(mesh.attributeFormat(i))) {
Error{} << prefix << "Decompression of the vertex data requires all attributes to be compressed";
return false;
}
}
}

if((configuration.value<bool>("encodeIndex") || configuration.value<bool>("decodeIndex")) && mesh.indexCount()%3 != 0) {
Error{} << prefix << "Only the index data mapped to triangles -hence divisible by 3- may be de/compressed";
return false;
}

return true;
}

Expand Down Expand Up @@ -319,6 +350,64 @@ template<std::size_t(*F)(UnsignedInt*, const UnsignedInt*, std::size_t, const Fl

}

std::vector<unsigned char> encodeVertex(const MeshData& mesh) {
auto owned_mesh = MeshTools::owned(mesh);
std::vector<unsigned char> buffer;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a Containers::Array<UnsignedByte> (UnsignedByte is unsigned char)

You then need to use Containers::arrayReserve() and Containers::arrayResize() functions.

if(MeshTools::isInterleaved(mesh)) {
buffer.resize(meshopt_encodeVertexBufferBound(owned_mesh.vertexCount(), owned_mesh.attributeStride(0)));
buffer.resize(meshopt_encodeVertexBuffer(&buffer[0], buffer.size(), owned_mesh.vertexData(), owned_mesh.vertexCount(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would then be buffer.data(), buffer.size(), ...

owned_mesh.attributeStride(0)));
} else {
buffer.reserve(owned_mesh.vertexData().size());

/* Non-interleaved vertex buffer is handled by encoding each attribute data separately due to the stride */
for(UnsignedInt i = 0; i < owned_mesh.attributeCount(); i++) {
auto format_size = sizeof(owned_mesh.attributeFormat(i));

std::vector<unsigned char> buf(meshopt_encodeVertexBufferBound(owned_mesh.vertexCount(), owned_mesh.attributeStride(i)));
if(owned_mesh.attributeName(i) == Trade::MeshAttribute::Position){
if(owned_mesh.attributeStride(i)/format_size == 2) {
buf.resize(meshopt_encodeVertexBuffer(&buf[0], buf.size(), owned_mesh.positions2DAsArray(), owned_mesh.vertexCount(), owned_mesh.attributeStride(i)));
}
else if(owned_mesh.attributeStride(i)/format_size == 3) {
buf.resize(meshopt_encodeVertexBuffer(&buf[0], buf.size(), owned_mesh.positions3DAsArray(), owned_mesh.vertexCount(), owned_mesh.attributeStride(i)));
}
}
if(owned_mesh.attributeName(i) == Trade::MeshAttribute::TextureCoordinates) {
buf.resize(meshopt_encodeVertexBuffer(&buf[0], buf.size(), owned_mesh.textureCoordinates2DAsArray(), owned_mesh.vertexCount(), owned_mesh.attributeStride(i)));
}
if(owned_mesh.attributeName(i) == Trade::MeshAttribute::Color) {
buf.resize(meshopt_encodeVertexBuffer(&buf[0], buf.size(), owned_mesh.colorsAsArray(), owned_mesh.vertexCount(), owned_mesh.attributeStride(i)));
}
std::move(buf.begin(), buf.end(), std::back_inserter(buffer));
}
buffer.shrink_to_fit();
}
CORRADE_INTERNAL_ASSERT(!buffer.empty() && buffer.size() < owned_mesh.vertexData().size());
return buffer;
}

Containers::Array<char> decodeVertex(const MeshData& mesh) {
auto owned_mesh = MeshTools::owned(mesh);
if(MeshTools::isInterleaved(owned_mesh)) {
Containers::Array<char> decoded{NoInit, owned_mesh.vertexCount()*owned_mesh.attributeStride(0)};
Containers::ArrayView<const unsigned char> encoded_data = Containers::arrayCast<const unsigned char>(owned_mesh.vertexData());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Containers::ArrayView<const unsigned char> encoded_data = Containers::arrayCast<const unsigned char>(owned_mesh.vertexData());
Containers::ArrayView<const unsigned char> encodedData = Containers::arrayCast<const UnsignedByte>(owned_mesh.vertexData());

Again, just minor coding style things

int result = meshopt_decodeVertexBuffer(decoded.data(), owned_mesh.vertexCount(), owned_mesh.attributeStride(0),
encoded_data.data(), encoded_data.size());
return decoded;
} else {
auto last_attribute_size = owned_mesh.vertexCount()*owned_mesh.attributeStride(owned_mesh.attributeCount()-1);
Containers::Array<char> decoded{NoInit, owned_mesh.attributeOffset(owned_mesh.attributeCount()-1)+last_attribute_size};
for(unsigned int i=0; i < owned_mesh.attributeCount(); ++i) {
Containers::ArrayView<const unsigned char> data = Containers::arrayCast<const unsigned char>(owned_mesh.attribute(i).asContiguous());
const std::size_t vertexSize = owned_mesh.attributeStride(i);
int result = meshopt_decodeVertexBuffer(decoded.data(), owned_mesh.vertexCount(), vertexSize,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

result is not being used yet... probably needs to be checked for errors?

data, data.size());
}
return decoded;
}
}

Containers::Optional<MeshData> MeshOptimizerSceneConverter::doConvert(const MeshData& mesh) {
/* If the mesh is indexed with an implementation-specific index type,
interleave() won't be able to turn its index buffer into a contiguous
Expand Down Expand Up @@ -419,6 +508,70 @@ Containers::Optional<MeshData> MeshOptimizerSceneConverter::doConvert(const Mesh
populatePositions(out, positionStorage, positions);
}

if(configuration().value<bool>("encodeIndex")) {
out = MeshTools::owned(mesh);

std::vector<unsigned char> index_buffer(meshopt_encodeIndexBufferBound(out.indexCount(), out.vertexCount()));
index_buffer.resize(meshopt_encodeIndexBuffer(&index_buffer[0], index_buffer.size(), out.indexData().data(), out.indexCount()));

if(index_buffer.empty() || index_buffer.size() > out.indexData().size()) {
Error{} << "Trade::MeshOptimizerSceneConverter::convert(): index buffer encoding failed: encoded buffer size:"
<< index_buffer.size() << "original index data size:" << out.indexData().size();
return Containers::NullOpt;
}

out = Trade::MeshData{out.primitive(), {}, index_buffer, Trade::MeshIndexData{index_buffer}, {},
out.vertexData(), out.releaseAttributeData(), out.vertexCount()};
}

if(configuration().value<bool>("decodeIndex")) {
out = MeshTools::owned(mesh);
Containers::Array<unsigned int> decodedIndex{NoInit, out.indexCount()*meshIndexTypeSize(out.indexType())};
int result = meshopt_decodeIndexBuffer(decodedIndex.data(), out.indexCount(),
reinterpret_cast<const unsigned char *>(out.indexData().data()), out.indexData().size());
if(result != 0) {
Error{} << "Trade::MeshOptimizerSceneConverter::convert(): index buffer decoding failed";
return Containers::NullOpt;
}

out = Trade::MeshData{out.primitive(), {}, decodedIndex, Trade::MeshIndexData{decodedIndex}, {},
out.vertexData(), out.releaseAttributeData(), out.vertexCount()};
}

if(configuration().value<bool>("encodeVertex")) {
meshopt_encodeVertexVersion(0);

out = MeshTools::owned(mesh);
auto attributes = MeshTools::owned(mesh).releaseAttributeData();
for(UnsignedInt i = 0; i < out.attributeCount(); ++i) {
const VertexFormat encodedFormat = !isVertexFormatImplementationSpecific(out.attributeFormat(i)) ?
vertexFormatWrap(out.attributeFormat(i)) : out.attributeFormat(i);
attributes[i] = Trade::MeshAttributeData{out.attributeName(i), encodedFormat,
out.attributeOffset(i), out.vertexCount(),
out.attributeStride(i), out.attributeArraySize(i)};
}
std::vector<unsigned char> encoded_buffer = encodeVertex(out);
out = Trade::MeshData{out.primitive(), Trade::DataFlags{}, out.indexData(), Trade::MeshIndexData{out.indices()}, Trade::DataFlag::Mutable,
encoded_buffer, std::move(attributes)};
}

if(configuration().value<bool>("decodeVertex")) {
out = MeshTools::owned(mesh);
auto attributes = MeshTools::owned(mesh).releaseAttributeData();
for(UnsignedInt i = 0; i != out.attributeCount(); ++i) {
const VertexFormat originalFormat = vertexFormatUnwrap<VertexFormat>(out.attributeFormat(i));
attributes[i] = Trade::MeshAttributeData{out.attributeName(i), originalFormat,
out.attributeOffset(i), out.vertexCount(),
out.attributeStride(i), out.attributeArraySize(i)};
}
Containers::Array<char> decoded{NoInit, out.vertexCount()*out.attributeStride(0)};
Containers::ArrayView<const unsigned char> encoded_data = Containers::arrayCast<const unsigned char>(out.vertexData());
int result = meshopt_decodeVertexBuffer(decoded.data(), out.vertexCount(), out.attributeStride(0),
encoded_data.data(), encoded_data.size());
out = Trade::MeshData{out.primitive(), Trade::DataFlags{}, out.indexData(), Trade::MeshIndexData{out.indices()}, Trade::DataFlag::Mutable,
decoded, std::move(attributes)};
}

/* Print before & after stats if verbose output is requested */
if(flags() & SceneConverterFlag::Verbose)
analyzePost("Trade::MeshOptimizerSceneConverter::convert():", out, configuration(), positions, vertexSize, vertexCacheStatsBefore, vertexFetchStatsBefore, overdrawStatsBefore);
Expand Down
Loading