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

Conversation

melikebatihan
Copy link
Contributor

Hey @mosra, @Squareys,
here is the first outlook on mesh compression.

I have used “TexturedQuadExample” from magnum examples to figure out how mesh compression of meshoptimizer library works and created interleaved and non-interleaved mesh data using the position, texture, and color data in the example. Later on, I adapted my work there to the plugin, but got the following errors when I started testing:

Index Compression

When wrapped in a MeshData instance as the following, the encoded index data can’t be decoded because the encoded indexData.size() gets assigned to the indexCount(), hence the requirement ‘index_count % 3 == 0' fails:
out = Trade::MeshData{primitive, {}, encoded_buffer, Trade::MeshIndexData{encoded_buffer}, {}, vertexData, attributeData, vertexCount};

So, in the case above out.indexCount() yields out.indexData.size() instead of the number of indices.

Vertex Compression

1- With interleaved mesh, encoded data can be put in a MeshData instance only when all attribute spans are smaller than the encoded data size, which can be achieved only with short vertex buffers as in the test example MeshOptimizerSceneConverterTest::encodeDecodeInterleavedMesh(). However, an encoded vertex data successfully wrapped in a MeshData instance can’t be used as an argument in doConvert() for decoding because the attribute information doesn’t match the vertex data information.

When the number of vertices is increased as in the example MeshOptimizerSceneConverterTest::encodeInterleavedLongMesh(), an error about an attribute span incompatible with the given encoded vertex data occurs, hence no MeshData instance with the encoded data in it can be created.

2- With non-interleaved mesh, I’ve done encoding attribute by attribute and merged encoded buffers into one to wrap it with a MeshData instance, which makes decoding problematic because decoding also needs to be performed separately on each attribute but encoded MeshData can’t give any information on where each attribute data starts and ends. I tried putting this info in the MeshData instance by changing the offset of the encoded data in accordance with the size of each encoded attribute data but it returned with an attribute span error.

@mosra mosra added this to the 2022.0a milestone Aug 29, 2022
@mosra
Copy link
Owner

mosra commented Aug 29, 2022

Thank you! Happy to see this code.

I'll hopefully have time to look into this during the weekend, need to refresh my memory first on what we wanted to do here :) And the issues you mention could very well be stupid bugs on my side, I tried my best to prepare MeshData for this but might not be handling everything correctly yet.

Copy link
Contributor

@Squareys Squareys left a comment

Choose a reason for hiding this comment

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

Hey @melikebatihan!

I gave this a first pass, mainly looking at coding style, formatting and such. @mosra will have a closer look at the actual implementation.

Generally, try to use Containers::Array instead of std::vector where possible, on the one hand, this will require you to write code with better allocation behavior (from which standpoint your code looks pretty good either way already, though), and it might allow avoiding the <vector> header for improved compile times.

The UnsignedByte etc types exist to avoid potential differences between platforms/compilers. While rare, we use them consistently, so that accidental uses of the standard types stand out in the code.

@@ -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

@@ -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.

}

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 :)

if(configuration.value<bool>("encodeVertex")) {
unsigned int encoded_attribute_count = 0;
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)

@@ -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.

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?

Vector4 color[8];
} vertices;

const Vector2 positions[]{{ 0.5f, -0.5f}, { 0.5f, 0.5f}, {-0.5f, -0.5f}, {-0.5f, 0.5f}, {-1.0f, -0.5f}, {-1.0f, 0.5f}, {-1.0f, 0.5f}, {-1.0f, 0.5f}};
Copy link
Contributor

Choose a reason for hiding this comment

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

There's some extra spaces here that could be cleaned up :)

Comment on lines +1264 to +1278
Trade::DataFlags{}, indices, Trade::MeshIndexData{indices},
Trade::DataFlag::Mutable, vertexData, {
Trade::MeshAttributeData{Trade::MeshAttribute::Position,
Containers::StridedArrayView1D<const Vector2>{
Containers::arrayView(vertexData), vertices.position,
Containers::arraySize(positions), sizeof(Vector2)}},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates,
Containers::StridedArrayView1D<const Vector2>{
Containers::arrayView(vertexData), vertices.textureCoordinate,
Containers::arraySize(textureCoordinates), sizeof(Vector2)}},
Trade::MeshAttributeData{Trade::MeshAttribute::Color,
Containers::StridedArrayView1D<const Vector4>{
Containers::arrayView(vertexData), vertices.color,
Containers::arraySize(colors), sizeof(Vector4)}},
}};
Copy link
Contributor

Choose a reason for hiding this comment

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

You might want to reduce the indentation here, to make it easier to read

const Vector2 textureCoordinates[]{{1.0f, 0.0f}, {1.0f, 1.0f}, {0.0f, 0.0f}, {0.0f, 1.0f}, {0.8f, 0.0f}, {1.0f, 0.8f}, {1.0f, 0.8f}, {1.0f, 0.8f}};
const Vector4 colors[]{{255,0,0,0}, {0,255,0,0}, {0,0,255,0}, {150,70,30,0}, {30,70,150,0}, {30,150,70,0}, {30,150,70,0}, {30,150,70,0}};

for(UnsignedInt i = 0; i < 8; i++) {
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
for(UnsignedInt i = 0; i < 8; i++) {
for(UnsignedInt i = 0; i < 8; ++i) {

Ultra nit-picky, as above, sorry! :)

Containers::Pointer<AbstractSceneConverter> converter = _manager.instantiate("MeshOptimizerSceneConverter");
converter->configuration().setValue("encodeVertex", true);

struct QuadVertex {
Copy link
Contributor

Choose a reason for hiding this comment

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

What makes this vertex a "Quad" rather than just a vertex? Possibly just Vertex is sufficient :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants