From 3a25908baa700baa5662809ab2213be193adfac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Delgado=20Kr=C3=A4mer?= Date: Thu, 7 Nov 2024 22:42:51 +0100 Subject: [PATCH] WIP --- CMakeLists.txt | 9 +- README.md | 1 + cmake/Finddraco.cmake | 32 +++++ src/libguc/CMakeLists.txt | 18 ++- src/libguc/src/cgltf_util.cpp | 251 +++++++++++++++++++++++++++++++++- 5 files changed, 306 insertions(+), 5 deletions(-) create mode 100644 cmake/Finddraco.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 749eb27..720b782 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + # Find USD - only non-monolithic builds have been tested. find_package(pxr CONFIG REQUIRED) @@ -14,9 +16,14 @@ find_package(pxr CONFIG REQUIRED) find_package(MaterialX 1.38.6 REQUIRED HINTS ${pxr_DIR}) # We need to open PNG and JPEG files in order to read the number of channels -# for shading node creation. OIIO should be provided by the USD installation. +# for shading node creation. OIIO can be provided by the USD installation, +# otherwise we fall back to stb_image. find_package(OpenImageIO HINTS ${pxr_DIR}) +# Optionally support draco. See Finddraco.cmake for more information. +set(draco_ROOT ${pxr_DIR}) +find_package(draco) + option(GUC_BUILD_EXECUTABLE "Build the guc executable." ON) option(GUC_BUILD_USDGLTF "Build the Sdf file format plugin." OFF) diff --git a/README.md b/README.md index eea5b5e..4fd6b4d 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ An example asset conversion is described in the [Structure Mapping](docs/Structu Name | Status                         ------------------------------------|---------- EXT_meshopt_compression | ✅ Complete +KHR_draco_mesh_compression | ✅ Complete KHR_lights_punctual | ✅ Partial 1 KHR_materials_clearcoat | ✅ Complete KHR_materials_emissive_strength | ✅ Complete diff --git a/cmake/Finddraco.cmake b/cmake/Finddraco.cmake new file mode 100644 index 0000000..ea21ae7 --- /dev/null +++ b/cmake/Finddraco.cmake @@ -0,0 +1,32 @@ +# +# Finds draco and populates following variables: +# DRACO_FOUND (draco_FOUND) +# DRACO_INCLUDE_DIR +# DRACO_LIBRARIES +# +# The reason why we ship our custom Find* file is that the draco version +# that USD uses, 1.3.6, has a broken CMake config file. +# +# This also caused trouble for USD, which is why it comes with a custom +# Find* script similar to this one. However, the script has two issues: +# 1) It specifies full file names, causing 'libdraco.1.dylib' to not +# be found on macOS. +# 2) It only finds the main draco lib, but not the decoder-specific +# lib which we need in guc. +# + +find_path(DRACO_INCLUDE_DIR NAMES "draco/core/draco_version.h") + +find_library(DRACO_LIBRARY NAMES draco PATH_SUFFIXES lib) +find_library(DRACO_DEC_LIBRARY NAMES dracodec PATH_SUFFIXES lib) + +set(DRACO_LIBRARIES ${DRACO_LIBRARY} ${DRACO_DEC_LIBRARY}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(draco + REQUIRED_VARS + DRACO_INCLUDE_DIR + DRACO_DEC_LIBRARY + DRACO_LIBRARY + DRACO_LIBRARIES +) diff --git a/src/libguc/CMakeLists.txt b/src/libguc/CMakeLists.txt index 850c565..2f60fa8 100644 --- a/src/libguc/CMakeLists.txt +++ b/src/libguc/CMakeLists.txt @@ -62,6 +62,11 @@ else() list(APPEND LIBGUC_SHARED_LIBRARIES $) endif() +if(draco_FOUND) + list(APPEND LIBGUC_DEFINES "GUC_USE_DRACO") + list(APPEND LIBGUC_SHARED_LIBRARIES ${DRACO_LIBRARIES}) +endif() + # # libguc # @@ -75,10 +80,15 @@ add_library( target_link_libraries(libguc PRIVATE ${LIBGUC_SHARED_LIBRARIES}) target_include_directories(libguc - PUBLIC $ - PUBLIC $ + PUBLIC + $ + $ ) +if(draco_FOUND) + target_include_directories(libguc PRIVATE ${DRACO_INCLUDE_DIRS}) +endif() + set_target_properties( libguc PROPERTIES @@ -156,6 +166,10 @@ if(GUC_BUILD_USDGLTF) target_include_directories(usdGlTF PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") + if(draco_FOUND) + target_include_directories(usdGlTF PRIVATE ${DRACO_INCLUDE_DIRS}) + endif() + target_compile_definitions( usdGlTF PRIVATE diff --git a/src/libguc/src/cgltf_util.cpp b/src/libguc/src/cgltf_util.cpp index e169564..4bf6894 100644 --- a/src/libguc/src/cgltf_util.cpp +++ b/src/libguc/src/cgltf_util.cpp @@ -27,7 +27,11 @@ #include -#include +#ifdef GUC_USE_DRACO +#include +#include +#endif + #include #include @@ -38,6 +42,7 @@ using namespace PXR_NS; namespace detail { constexpr static const char* GLTF_EXT_MESHOPT_COMPRESSION_EXTENSION_NAME = "EXT_meshopt_compression"; + constexpr static const char* GLTF_KHR_DRACO_MESH_COMPRESSION_EXTENSION_NAME = "KHR_draco_mesh_compression"; bool extensionSupported(const char* name) { @@ -55,7 +60,8 @@ namespace detail strcmp(name, "KHR_materials_volume") == 0 || strcmp(name, "KHR_mesh_quantization") == 0 || strcmp(name, "KHR_texture_transform") == 0 || - strcmp(name, GLTF_EXT_MESHOPT_COMPRESSION_EXTENSION_NAME) == 0; + strcmp(name, GLTF_EXT_MESHOPT_COMPRESSION_EXTENSION_NAME) == 0 || + strcmp(name, GLTF_KHR_DRACO_MESH_COMPRESSION_EXTENSION_NAME) == 0; } struct BufferHolder @@ -118,6 +124,221 @@ namespace detail bufferHolder->map.erase(bufferPtr); } +#ifdef GUC_USE_DRACO + template + bool convertDracoVertexAttributes(const draco::PointAttribute* attribute, + size_t vertexCount, + uint8_t* result) + { + uint32_t elemSize = sizeof(T) * attribute->num_components(); + + const static uint32_t MAX_COMPONENT_COUNT = 4; + + T elems[MAX_COMPONENT_COUNT]; + for (size_t i = 0; i < vertexCount; i++) + { + draco::PointIndex pointIndex(i); + draco::AttributeValueIndex valueIndex = attribute->mapped_index(pointIndex); + + if (!attribute->ConvertValue(valueIndex, attribute->num_components(), elems)) + { + return false; + } + + uint32_t elemOffset = i * elemSize; + memcpy(&result[elemOffset], &elems[0], elemSize); + } + return true; + } + + bool convertDracoVertexAttributes(cgltf_component_type componentType, + const draco::PointAttribute* attribute, + size_t vertexCount, + uint8_t* result) + { + bool success = false; + switch (componentType) + { + case cgltf_component_type_r_8: + success = convertDracoVertexAttributes(attribute, vertexCount, result); + case cgltf_component_type_r_8u: + success = convertDracoVertexAttributes(attribute, vertexCount, result); + case cgltf_component_type_r_16: + success = convertDracoVertexAttributes(attribute, vertexCount, result); + case cgltf_component_type_r_16u: + success = convertDracoVertexAttributes(attribute, vertexCount, result); + case cgltf_component_type_r_32u: + success = convertDracoVertexAttributes(attribute, vertexCount, result); + case cgltf_component_type_r_32f: + success = convertDracoVertexAttributes(attribute, vertexCount, result); + break; + default: + break; + } + return success; + } + + bool decompressDraco(cgltf_data* data) + { + // TODO: rename Accessor -> Attribute + // TODO: further var rename + cleanup + + // + // Phase 1: TODO description + // + struct DenseAccessorComponentType // TODO: rename since not only type + { + cgltf_accessor* srcAccessor; + int dracoUid; + }; + using DenseAccessorComponentTypes = std::vector; + + std::map accessorsToDecompress; // TODO: rename? + + for (size_t i = 0; i < data->meshes_count; ++i) + { + const cgltf_mesh& mesh = data->meshes[i]; + + for (size_t j = 0; j < mesh.primitives_count; j++) + { + const cgltf_primitive& primitive = mesh.primitives[j]; + + if (!primitive.has_draco_mesh_compression) + { + continue; + } + + const cgltf_draco_mesh_compression* draco = &primitive.draco_mesh_compression; + + if (accessorsToDecompress.count(draco) == 0) + { + accessorsToDecompress[draco] = {}; + } + + DenseAccessorComponentTypes& mapEnty = accessorsToDecompress[draco]; + +// TODO: "The attributes defined in the extension must be a subset of the attributes of the primitive." -> should be fine like this + for (size_t i = 0; i < draco->attributes_count; i++) + { + const cgltf_attribute* dracoAttr = &draco->attributes[i]; + cgltf_attribute* gltfAttr = nullptr; + + for (size_t j = 0; j < primitive.attributes_count; j++) + { + cgltf_attribute* newAttr = &primitive.attributes[i]; + + if (strcmp(newAttr->name, dracoAttr->name) == 0) + { + gltfAttr = newAttr; + break; + } + } + + if (!gltfAttr || !gltfAttr->data) // TODO: spec violation? if so, leave comment + { + TF_RUNTIME_ERROR("Draco attributes do not match source attributes"); + return false; + } + + auto dracoUid = int(cgltf_accessor_index(data, dracoAttr->data)); + mapEnty.push_back({ gltfAttr->data, dracoUid }); + } + + primitive.indices->offset = 0; + primitive.indices->stride = sizeof(uint32_t); + primitive.indices->buffer_view = draco->buffer_view; + } + } + + // + // Phase 2: TODO description (explain constraints and trick) + // + for (auto it = accessorsToDecompress.begin(); it != accessorsToDecompress.end(); ++it) + { + // Decompress into mesh + const cgltf_draco_mesh_compression* draco = it->first; + cgltf_buffer_view* bufferView = draco->buffer_view; // TODO: can this be null? + cgltf_buffer* buffer = bufferView->buffer; // TODO: can this be null? + + const char* bufferData = &((const char*) buffer->data)[bufferView->offset]; + + draco::DecoderBuffer decoderBuffer; + decoderBuffer.Init(bufferData, bufferView->size); + + auto geomType = draco::Decoder::GetEncodedGeometryType(&decoderBuffer); + if (!geomType.ok() || geomType.value() != draco::TRIANGULAR_MESH) + { + TF_RUNTIME_ERROR("unsupported Draco geometry type"); + return false; + } + + draco::Decoder decoder; + auto decodeResult = decoder.DecodeMeshFromBuffer(&decoderBuffer); + if (!decodeResult.ok()) + { + TF_RUNTIME_ERROR("Draco failed to decode mesh from buffer"); + return false; + } + + const std::unique_ptr& mesh = decodeResult.value(); + + // Allocate decoded buffer + const DenseAccessorComponentTypes& attrsToDecode = it->second; + + uint32_t vertexCount = mesh->num_points(); + uint32_t faceCount = mesh->num_faces(); + uint32_t indexCount = faceCount * 3; + + uint32_t indicesSize = indexCount * sizeof(uint32_t); + + size_t attributesSize = 0; + for (const DenseAccessorComponentType& c : attrsToDecode) + { + cgltf_accessor* srcAccessor = c.srcAccessor; + + attributesSize += vertexCount * srcAccessor->stride; + } + + bufferView->data = malloc(indicesSize + attributesSize); + + // Write decoded data + auto baseIndex = draco::FaceIndex(0); + TF_VERIFY(sizeof(mesh->face(baseIndex)[0]) == 4); + memcpy(bufferView->data, &mesh->face(baseIndex)[0], indicesSize); + + size_t attributeOffset = indicesSize; + for (const DenseAccessorComponentType& c : attrsToDecode) + { + const draco::PointAttribute* dracoAttr = mesh->GetAttributeByUniqueId(c.dracoUid); + if (!dracoAttr) + { + TF_RUNTIME_ERROR("invalid Draco attribute Uid"); + return false; + } + + cgltf_accessor* srcAccessor = c.srcAccessor; + + TF_VERIFY(srcAccessor->count == vertexCount); + if (!convertDracoVertexAttributes(srcAccessor->component_type, + dracoAttr, + vertexCount, + &((uint8_t*) bufferView->data)[attributeOffset])) + { + TF_RUNTIME_ERROR("failed to decode Draco attribute"); + return false; + } + + srcAccessor->buffer_view = draco->buffer_view; + srcAccessor->offset = attributeOffset; + + attributeOffset += vertexCount * srcAccessor->stride; + } + } + + return true; + } +#endif + // Based on https://github.com/jkuhlmann/cgltf/pull/129 cgltf_result decompressMeshopt(cgltf_data* data) { @@ -241,6 +462,9 @@ namespace guc } bool meshoptCompressionRequired = false; +#ifdef GUC_USE_DRACO + bool dracoMeshCompressionRequired = false; +#endif for (size_t i = 0; i < (*data)->extensions_required_count; i++) { @@ -252,6 +476,13 @@ namespace guc meshoptCompressionRequired = true; } +#ifdef GUC_USE_DRACO + if (strcmp(ext, detail::GLTF_KHR_DRACO_MESH_COMPRESSION_EXTENSION_NAME) == 0) + { + dracoMeshCompressionRequired = true; + } +#endif + if (detail::extensionSupported(ext)) { continue; @@ -278,6 +509,22 @@ namespace guc TF_WARN(errStr, guc::cgltf_error_string(result)); } +#ifdef GUC_USE_DRACO + if (!detail::decompressDraco(*data)) + { + const char* errStr = "unable to decode Draco data"; + + if (dracoMeshCompressionRequired) + { + TF_RUNTIME_ERROR("%s", errStr); + free_gltf(*data); + return false; + } + + TF_WARN("%s", errStr); + } +#endif + for (size_t i = 0; i < (*data)->extensions_used_count; i++) { const char* ext = (*data)->extensions_used[i];