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

Initial C API for FFI #663

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
18 changes: 17 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,11 @@ list(
"${draco_src_root}/javascript/emscripten/draco_animation_encoder_glue_wrapper.cc"
)

list(
APPEND draco_c_api_dec_sources
"${draco_src_root}/c_api/c_api_decoder.cc"
)

list(APPEND draco_unity_plug_sources
"${draco_src_root}/unity/draco_unity_plugin.cc"
"${draco_src_root}/unity/draco_unity_plugin.h")
Expand Down Expand Up @@ -773,6 +778,16 @@ else()
${draco_defines}
INCLUDES
${draco_include_paths})
draco_add_library(NAME
draco_c_api_dec
TYPE
OBJECT
SOURCES
${draco_c_api_dec_sources}
DEFINES
${draco_defines}
INCLUDES
${draco_include_paths})

set(draco_object_library_deps
draco_attributes
Expand Down Expand Up @@ -801,7 +816,8 @@ else()
draco_animation_enc
draco_point_cloud
draco_points_dec
draco_points_enc)
draco_points_enc
draco_c_api_dec)

# Library targets that consume the object collections.
if(MSVC OR WIN32)
Expand Down
6 changes: 6 additions & 0 deletions cmake/draco_options.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ macro(draco_set_default_options)
"Enable attribute deduping." VALUE OFF)
draco_option(NAME DRACO_TESTS HELPSTRING "Enables tests." VALUE OFF)
draco_option(NAME DRACO_WASM HELPSTRING "Enables WASM support." VALUE OFF)
draco_option(NAME DRACO_C_API HELPSTRING
"Build C wrapper." VALUE OFF)
draco_option(NAME DRACO_UNITY_PLUGIN HELPSTRING
"Build plugin library for Unity." VALUE OFF)
draco_option(NAME DRACO_ANIMATION_ENCODING HELPSTRING "Enable animation."
Expand Down Expand Up @@ -170,6 +172,10 @@ macro(draco_set_optional_features)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()

if(DRACO_C_API)
draco_enable_feature(FEATURE "DRACO_C_API_SUPPORTED")
endif()

endmacro()

# Macro that handles tracking of Draco preprocessor symbols for the purpose of
Expand Down
3 changes: 2 additions & 1 deletion cmake/draco_tests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ list(
"${draco_src_root}/metadata/metadata_encoder_test.cc"
"${draco_src_root}/metadata/metadata_test.cc"
"${draco_src_root}/point_cloud/point_cloud_builder_test.cc"
"${draco_src_root}/point_cloud/point_cloud_test.cc")
"${draco_src_root}/point_cloud/point_cloud_test.cc"
"${draco_src_root}/c_api/c_api_test.cc")

list(APPEND draco_gtest_all
"${draco_root}/../googletest/googletest/src/gtest-all.cc")
Expand Down
95 changes: 95 additions & 0 deletions src/draco/c_api/c_api.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright 2020 The Draco Authors.
//
// 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.
//
#ifndef DRACO_C_API_H_
#define DRACO_C_API_H_

#include <stdint.h>
#include <stdbool.h>

#ifdef __cplusplus
extern "C" {
#endif

// If compiling with Visual Studio.
#if defined(_MSC_VER)
#define EXPORT_API __declspec(dllexport)
qmuntal marked this conversation as resolved.
Show resolved Hide resolved
#else
// Other platforms don't need this.
Copy link
Contributor

Choose a reason for hiding this comment

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

GCC and Clang do need an EXPORT_API definition since GCC v4
It is strongly recommended not to export all functions, especially for a C-API as it greatly reduces the chance of name collisions and speeds up the dylib/so loader.

The draco C-API so/dylib should be compiled with the flag -fvisibility=hidden, and the following added for GCC and clang compilers:

#ifdef DRACO_BUILDING_DLL // also update draco_targets.cmake
#define EXPORT_API __attribute__ ((visibility ("default")))
#else
#define EXPORT_API
#endif

Copy link
Contributor Author

@qmuntal qmuntal Feb 1, 2021

Choose a reason for hiding this comment

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

This is a little bit tricky to implement with the current approach of building the C-API as part of the "core" library, but I do agree it should happen.

The problem I'm facing if only the C-API is exported then the executables (draco_decoder and draco_encoder) nor the tests can be correctly linked as those depend on the C++ API.

I'm starting to lean towards having a separate library for the C-API, as the CMake orchestration would be much more easy to manage and the DRACO_C_API will be orthogonal to other features. What do you think @RichardTea?

Copy link
Contributor

@RichardTea RichardTea Feb 1, 2021

Choose a reason for hiding this comment

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

I think it's reasonable to pick an API for your shared libraries at configure time, as I'm reasonably sure that most consumers of the C-API will not want to use the C++ version of the dynamic library anyway.

The whole point of a C-API is to ensure a consistent ABI, so exposing all the C++-mangled stuff could rather complicate things on some compilers.

If it's difficult to build all four at the same time then I think draco_encoder, draco_decoder and the C-API shared lib could all consume the same C++ static library "privately" to achieve the goal of hiding all the internal symbols - IIRC, symbols aren't ever truly private in a static library so that should still work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've finally moved the C-API to its own library called cdraco.[dll/lib/so/a/...], gated by the DRACO_C_API flag. This library is self contained and the dynamic version just exports the C-API, all the C++ stuff is hidden.

#define EXPORT_API
#endif // defined(_MSC_VER)

typedef const char* draco_string; // NULL terminated

// draco::Status

typedef struct draco_status draco_status;

EXPORT_API void dracoStatusRelease(draco_status *status);

EXPORT_API int dracoStatusCode(const draco_status *status);

EXPORT_API bool dracoStatusOk(const draco_status *status);

// Returns the status message.
// The memory backing memory is valid meanwhile status is not released.
EXPORT_API draco_string dracoStatusErrorMsg(const draco_status *status);

// draco::Mesh

typedef uint32_t draco_face[3];
typedef struct draco_mesh draco_mesh;

EXPORT_API draco_mesh* dracoNewMesh();

EXPORT_API void dracoMeshRelease(draco_mesh *mesh);

EXPORT_API uint32_t dracoMeshNumFaces(const draco_mesh *mesh);

EXPORT_API uint32_t dracoMeshNumPoints(const draco_mesh *mesh);

// Queries an array of 3*face_count elements containing the triangle indices.
// out_values must be allocated to contain at least 3*face_count uint16_t elements.
// out_size must be exactly 3*face_count*sizeof(uint16_t), else out_values
// won´t be filled and returns false.
EXPORT_API bool dracoMeshGetTrianglesUint16(const draco_mesh *mesh,
const size_t out_size,
uint16_t *out_values);

// Queries an array of 3*face_count elements containing the triangle indices.
// out_values must be allocated to contain at least 3*face_count uint32_t elements.
// out_size must be exactly 3*face_count*sizeof(uint32_t), else out_values
// won´t be filled and returns false.
EXPORT_API bool dracoMeshGetTrianglesUint32(const draco_mesh *mesh,
const size_t out_size,
uint32_t *out_values);

// draco::Decoder

typedef struct draco_decoder draco_decoder;

EXPORT_API draco_decoder* dracoNewDecoder();

EXPORT_API void dracoDecoderRelease(draco_decoder *decoder);

EXPORT_API draco_status* dracoDecoderArrayToMesh(draco_decoder *decoder,
const char *data,
size_t data_size,
draco_mesh *out_mesh);

#ifdef __cplusplus
}
#endif

#endif // DRACO_C_API_H_
115 changes: 115 additions & 0 deletions src/draco/c_api/c_api_decoder.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2020 The Draco Authors.
//
// 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 "draco/draco_features.h"

#ifdef DRACO_C_API_SUPPORTED

#include <cstring>

#include "draco/c_api/c_api.h"
#include "draco/compression/decode.h"

void dracoStatusRelease(draco_status *status) {
free(status);
}

int dracoStatusCode(const draco_status *status) {
return reinterpret_cast<const draco::Status*>(status)->code();
}

bool dracoStatusOk(const draco_status *status) {
return reinterpret_cast<const draco::Status*>(status)->ok();
}

draco_string dracoStatusErrorMsg(const draco_status *status) {
auto msg = reinterpret_cast<const draco::Status*>(status)->error_msg();
return reinterpret_cast<draco_string>(msg);
}

draco_mesh* dracoNewMesh() {
return reinterpret_cast<draco_mesh*>(new draco::Mesh());
}

void dracoMeshRelease(draco_mesh *mesh) {
free(mesh);
}

uint32_t dracoMeshNumFaces(const draco_mesh *mesh) {
return reinterpret_cast<const draco::Mesh*>(mesh)->num_faces();
}


uint32_t dracoMeshNumPoints(const draco_mesh *mesh) {
return reinterpret_cast<const draco::Mesh*>(mesh)->num_points();
}

template <typename T>
bool GetTrianglesArray(const draco::Mesh *m, const size_t out_size,
T *out_values) {
const uint32_t num_faces = m->num_faces();
if (num_faces * 3 * sizeof(T) != out_size) {
return false;
}

for (uint32_t face_id = 0; face_id < num_faces; ++face_id) {
const draco::Mesh::Face &face = m->face(draco::FaceIndex(face_id));
out_values[face_id * 3 + 0] = static_cast<T>(face[0].value());
out_values[face_id * 3 + 1] = static_cast<T>(face[1].value());
out_values[face_id * 3 + 2] = static_cast<T>(face[2].value());
}
return true;
}

bool dracoMeshGetTrianglesUint16(const draco_mesh *mesh, const size_t out_size,
uint16_t *out_values) {
auto m = reinterpret_cast<const draco::Mesh*>(mesh);
if (m->num_points() > std::numeric_limits<uint16_t>::max()) {
return false;
}

return GetTrianglesArray(m, out_size, out_values);
}

bool dracoMeshGetTrianglesUint32(const draco_mesh *mesh, const size_t out_size,
uint32_t *out_values) {
auto m = reinterpret_cast<const draco::Mesh*>(mesh);
if (m->num_points() > std::numeric_limits<uint32_t>::max()) {
return false;
}

return GetTrianglesArray(m, out_size, out_values);
}

draco_decoder* dracoNewDecoder() {
return reinterpret_cast<draco_decoder*>(new draco::Decoder());
}

void dracoDecoderRelease(draco_decoder *decoder) {
free(decoder);
}

draco_status* dracoDecoderArrayToMesh(draco_decoder *decoder,
const char *data,
size_t data_size,
draco_mesh *out_mesh) {
draco::DecoderBuffer buffer;
buffer.Init(data, data_size);
auto m = reinterpret_cast<draco::Mesh*>(out_mesh);
const auto &last_status_ = reinterpret_cast<draco::Decoder*>(decoder)->DecodeBufferToGeometry(&buffer, m);
return reinterpret_cast<draco_status*>(new draco::Status(last_status_));
}

#endif // DRACO_C_API_SUPPORTED
71 changes: 71 additions & 0 deletions src/draco/c_api/c_api_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2020 The Draco Authors.
//
// 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 "draco/draco_features.h"

#ifdef DRACO_C_API_SUPPORTED

#include <fstream>
#include <memory>
#include <sstream>
#include <string>

#include "draco/c_api/c_api.h"
#include "draco/core/draco_test_base.h"
#include "draco/core/draco_test_utils.h"

namespace {

draco_mesh *DecodeToDracoMesh(const std::string &file_name) {
std::ifstream input_file(draco::GetTestFileFullPath(file_name),
std::ios::binary);
if (!input_file) {
return nullptr;
}
// Read the file stream into a buffer.
std::streampos file_size = 0;
input_file.seekg(0, std::ios::end);
file_size = input_file.tellg() - file_size;
input_file.seekg(0, std::ios::beg);
std::vector<char> data(file_size);
input_file.read(data.data(), file_size);
if (data.empty()) {
return nullptr;
}

auto mesh = dracoNewMesh();
auto decoder = dracoNewDecoder();
dracoDecoderArrayToMesh(decoder, data.data(), data.size(), mesh);
dracoDecoderRelease(decoder);
return mesh;
}

TEST(DracoCAPITest, TestDecode) {
auto mesh = DecodeToDracoMesh("test_nm.obj.edgebreaker.cl4.2.2.drc");
ASSERT_NE(mesh, nullptr);
auto num_faces = dracoMeshNumFaces(mesh);
ASSERT_EQ(num_faces, 170);
ASSERT_EQ(dracoPointCloudNumPoints(mesh), 99);

auto indices_size = 3 * num_faces * sizeof(uint32_t);
uint32_t *indices = (uint32_t *)malloc(indices_size);
ASSERT_TRUE(dracoMeshGetTrianglesUint32(mesh, indices_size, indices));
free(indices);
dracoMeshRelease(mesh);
}

} // namespace

#endif // DRACO_C_API_SUPPORTED