mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
Initial add of pxr compat API.
This commit is contained in:
@@ -23,6 +23,8 @@ option(TINYUSDZ_WITH_AUDIO "Build with Audio support(WAV and MP3)" ON)
|
||||
|
||||
option(TINYUSDZ_WITH_PYTHON "Build with Python binding through pybind11" OFF)
|
||||
|
||||
option(TINYUSDZ_WITH_PXR_COMPAT_API "Build with pxr compatible API" ON)
|
||||
|
||||
option(TINYUSDZ_WITH_BLENDER_ADDON "Build with Python module for Blender(`TINYUSDZ_WITH_PYTHON` is force set to ON" OFF)
|
||||
|
||||
# -- EXR --
|
||||
@@ -94,6 +96,10 @@ set(TINYUSDZ_SOURCES
|
||||
${PROJECT_SOURCE_DIR}/src/pprinter.cc
|
||||
)
|
||||
|
||||
if (TINYUSDZ_WITH_PXR_COMPAT_API)
|
||||
list(APPEND TINYUSDZ_SOURCES ${PROJECT_SOURCE_DIR}/src/pxr-compat.cc)
|
||||
endif ()
|
||||
|
||||
set(TINYUSDZ_C_API_SOURCES
|
||||
${PROJECT_SOURCE_DIR}/src/c-tinyusd.cc
|
||||
)
|
||||
|
||||
@@ -130,6 +130,7 @@ For Windows native build, we assume `ninja.exe` is installed on your system(You
|
||||
* OpenSubdiv code is included in TinyUSDZ repo. If you want to use external OpenSubdiv repo, specity the path to OpenSubdiv using `osd_DIR` cmake environment variable.
|
||||
* `TINYUSDZ_WITH_AUDIO` : Support loading audio(mp3 and wav).
|
||||
* `TINYUSDZ_WITH_EXR` : Support loading EXR format HDR texture through TinyEXR.
|
||||
* `TINYUSDZ_WITH_PXR_COMPAT_API` : Build with pxr compatible API.
|
||||
|
||||
#### clang-cl on Windows
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "matrix.h"
|
||||
#include "trackball.h"
|
||||
|
||||
#define PAR_SHAPES_IMPLEMENTATION
|
||||
#include "par_shapes.h" // For meshing
|
||||
|
||||
const float kPI = 3.141592f;
|
||||
@@ -48,8 +49,10 @@ bool ConvertToRenderMesh(const tinyusdz::GeomSphere& sphere, DrawGeomMesh* dst)
|
||||
|
||||
// TODO: Animated radius
|
||||
float radius = 1.0;
|
||||
if (auto p = tinyusdz::primvar::as_basic<double>(&sphere.radius)) {
|
||||
radius = (*p);
|
||||
if (sphere.radius.IsTimeSampled()) {
|
||||
// TODO
|
||||
} else {
|
||||
radius = sphere.radius.Get();
|
||||
}
|
||||
|
||||
// scale by radius
|
||||
@@ -57,47 +60,47 @@ bool ConvertToRenderMesh(const tinyusdz::GeomSphere& sphere, DrawGeomMesh* dst)
|
||||
dst->vertices[i] = par_mesh->points[i] * radius;
|
||||
}
|
||||
|
||||
std::vector<int> facevarying_indices;
|
||||
std::vector<uint32_t> facevertex_indices;
|
||||
std::vector<float> facevarying_normals;
|
||||
std::vector<float> facevarying_texcoords;
|
||||
|
||||
// Make uv and normal facevarying
|
||||
// ntriangles = slices * 2 + (stacks - 2) * slices * 2
|
||||
for (size_t i = 0; i < par_mesh->ntriangles; i++) {
|
||||
PAR_SHAPES_T vidx0 = triangles[3 * i + 0];
|
||||
PAR_SHAPES_T vidx1 = triangles[3 * i + 1];
|
||||
PAR_SHAPES_T vidx2 = triangles[3 * i + 2];
|
||||
PAR_SHAPES_T vidx0 = par_mesh->triangles[3 * i + 0];
|
||||
PAR_SHAPES_T vidx1 = par_mesh->triangles[3 * i + 1];
|
||||
PAR_SHAPES_T vidx2 = par_mesh->triangles[3 * i + 2];
|
||||
|
||||
facevarying_indices.push_back(vidx0);
|
||||
facevarying_indices.push_back(vidx1);
|
||||
facevarying_indices.push_back(vidx2);
|
||||
facevertex_indices.push_back(vidx0);
|
||||
facevertex_indices.push_back(vidx1);
|
||||
facevertex_indices.push_back(vidx2);
|
||||
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * idx0 + 0]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * idx0 + 1]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * idx0 + 2]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * vidx0 + 0]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * vidx0 + 1]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * vidx0 + 2]);
|
||||
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * idx1 + 0]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * idx1 + 1]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * idx1 + 2]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * vidx1 + 0]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * vidx1 + 1]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * vidx1 + 2]);
|
||||
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * idx2 + 0]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * idx2 + 1]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * idx2 + 2]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * vidx2 + 0]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * vidx2 + 1]);
|
||||
facevarying_normals.push_back(par_mesh->normals[3 * vidx2 + 2]);
|
||||
|
||||
facevarying_texcoords.push_back(par_mesh->tcoords[2 * idx0 + 0]);
|
||||
facevarying_texcoords.push_back(par_mesh->tcoords[2 * idx0 + 1]);
|
||||
facevarying_texcoords.push_back(par_mesh->tcoords[2 * vidx0 + 0]);
|
||||
facevarying_texcoords.push_back(par_mesh->tcoords[2 * vidx0 + 1]);
|
||||
|
||||
facevarying_texcoords.push_back(par_mesh->tcoords[2 * idx1 + 0]);
|
||||
facevarying_texcoords.push_back(par_mesh->tcoords[2 * idx1 + 1]);
|
||||
facevarying_texcoords.push_back(par_mesh->tcoords[2 * vidx1 + 0]);
|
||||
facevarying_texcoords.push_back(par_mesh->tcoords[2 * vidx1 + 1]);
|
||||
|
||||
facevarying_texcoords.push_back(par_mesh->tcoords[2 * idx2 + 0]);
|
||||
facevarying_texcoords.push_back(par_mesh->tcoords[2 * idx2 + 1]);
|
||||
facevarying_texcoords.push_back(par_mesh->tcoords[2 * vidx2 + 0]);
|
||||
facevarying_texcoords.push_back(par_mesh->tcoords[2 * vidx2 + 1]);
|
||||
}
|
||||
|
||||
par_shapes_free_mesh(par_mesh);
|
||||
|
||||
|
||||
dst->facevarying_indices = facevarying_indices;
|
||||
dst->facevertex_indices = facevertex_indices;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -156,7 +159,7 @@ bool ConvertToRenderMesh(const tinyusdz::GeomMesh& mesh, DrawGeomMesh* dst) {
|
||||
|
||||
if (f_count == 3) {
|
||||
for (size_t f = 0; f < f_count; f++) {
|
||||
dst->facevarying_indices.push_back(
|
||||
dst->facevertex_indices.push_back(
|
||||
mesh.faceVertexIndices[face_offset + f]);
|
||||
|
||||
if (facevarying_normals.size()) {
|
||||
@@ -187,11 +190,11 @@ bool ConvertToRenderMesh(const tinyusdz::GeomMesh& mesh, DrawGeomMesh* dst) {
|
||||
size_t f1 = f + 1;
|
||||
size_t f2 = f + 2;
|
||||
|
||||
dst->facevarying_indices.push_back(
|
||||
dst->facevertex_indices.push_back(
|
||||
mesh.faceVertexIndices[face_offset + f0]);
|
||||
dst->facevarying_indices.push_back(
|
||||
dst->facevertex_indices.push_back(
|
||||
mesh.faceVertexIndices[face_offset + f1]);
|
||||
dst->facevarying_indices.push_back(
|
||||
dst->facevertex_indices.push_back(
|
||||
mesh.faceVertexIndices[face_offset + f2]);
|
||||
|
||||
if (facevarying_normals.size()) {
|
||||
@@ -308,7 +311,7 @@ bool ConvertToRenderMesh(const tinyusdz::GeomMesh& mesh, DrawGeomMesh* dst) {
|
||||
|
||||
std::cout << "num points = " << dst->vertices.size() / 3 << "\n";
|
||||
std::cout << "num triangulated faces = "
|
||||
<< dst->facevarying_indices.size() / 3 << "\n";
|
||||
<< dst->facevertex_indices.size() / 3 << "\n";
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -468,7 +471,7 @@ bool Render(const RenderScene& scene, const Camera& cam, AOV* output) {
|
||||
|
||||
// Intersector functor.
|
||||
nanort::TriangleIntersector<> triangle_intersector(
|
||||
mesh.vertices.data(), mesh.facevarying_indices.data(),
|
||||
mesh.vertices.data(), mesh.facevertex_indices.data(),
|
||||
sizeof(float) * 3);
|
||||
|
||||
nanort::TriangleIntersection<> isect; // stores isect info
|
||||
@@ -483,9 +486,9 @@ bool Render(const RenderScene& scene, const Camera& cam, AOV* output) {
|
||||
float3 v1;
|
||||
float3 v2;
|
||||
|
||||
size_t vid0 = mesh.facevarying_indices[3 * isect.prim_id + 0];
|
||||
size_t vid1 = mesh.facevarying_indices[3 * isect.prim_id + 1];
|
||||
size_t vid2 = mesh.facevarying_indices[3 * isect.prim_id + 2];
|
||||
size_t vid0 = mesh.facevertex_indices[3 * isect.prim_id + 0];
|
||||
size_t vid1 = mesh.facevertex_indices[3 * isect.prim_id + 1];
|
||||
size_t vid2 = mesh.facevertex_indices[3 * isect.prim_id + 2];
|
||||
|
||||
v0[0] = mesh.vertices[3 * vid0 + 0];
|
||||
v0[1] = mesh.vertices[3 * vid0 + 1];
|
||||
@@ -633,7 +636,7 @@ bool RenderLines(int start_y, int end_y, const RenderScene& scene,
|
||||
|
||||
// Intersector functor.
|
||||
nanort::TriangleIntersector<> triangle_intersector(
|
||||
mesh.vertices.data(), mesh.facevarying_indices.data(),
|
||||
mesh.vertices.data(), mesh.facevertex_indices.data(),
|
||||
sizeof(float) * 3);
|
||||
nanort::TriangleIntersection<> isect; // stores isect info
|
||||
|
||||
@@ -647,9 +650,9 @@ bool RenderLines(int start_y, int end_y, const RenderScene& scene,
|
||||
float3 v1;
|
||||
float3 v2;
|
||||
|
||||
size_t vid0 = mesh.facevarying_indices[3 * isect.prim_id + 0];
|
||||
size_t vid1 = mesh.facevarying_indices[3 * isect.prim_id + 1];
|
||||
size_t vid2 = mesh.facevarying_indices[3 * isect.prim_id + 2];
|
||||
size_t vid0 = mesh.facevertex_indices[3 * isect.prim_id + 0];
|
||||
size_t vid1 = mesh.facevertex_indices[3 * isect.prim_id + 1];
|
||||
size_t vid2 = mesh.facevertex_indices[3 * isect.prim_id + 2];
|
||||
|
||||
v0[0] = mesh.vertices[3 * vid0 + 0];
|
||||
v0[1] = mesh.vertices[3 * vid0 + 1];
|
||||
@@ -779,13 +782,13 @@ bool RenderScene::Setup() {
|
||||
DrawGeomMesh& draw_mesh = draw_meshes[i];
|
||||
|
||||
nanort::TriangleMesh<float> triangle_mesh(
|
||||
draw_mesh.vertices.data(), draw_mesh.facevarying_indices.data(),
|
||||
draw_mesh.vertices.data(), draw_mesh.facevertex_indices.data(),
|
||||
sizeof(float) * 3);
|
||||
nanort::TriangleSAHPred<float> triangle_pred(
|
||||
draw_mesh.vertices.data(), draw_mesh.facevarying_indices.data(),
|
||||
draw_mesh.vertices.data(), draw_mesh.facevertex_indices.data(),
|
||||
sizeof(float) * 3);
|
||||
|
||||
bool ret = draw_mesh.accel.Build(draw_mesh.facevarying_indices.size() / 3,
|
||||
bool ret = draw_mesh.accel.Build(draw_mesh.facevertex_indices.size() / 3,
|
||||
triangle_mesh, triangle_pred);
|
||||
if (!ret) {
|
||||
std::cerr << "Failed to build BVH\n";
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
@@ -21,6 +22,10 @@
|
||||
|
||||
#include "nonstd/optional.hpp"
|
||||
#include "nonstd/variant.hpp"
|
||||
|
||||
#define any_CONFIG_NO_EXCEPTIONS (1)
|
||||
#include "nonstd/any.hpp"
|
||||
|
||||
#include "nonstd/string_view.hpp"
|
||||
|
||||
using namespace nonstd::literals; // _sv
|
||||
@@ -269,8 +274,6 @@ class Path {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValid() const { return valid; }
|
||||
|
||||
private:
|
||||
std::string prim_part; // full path
|
||||
std::string prop_part; // full path
|
||||
@@ -689,6 +692,7 @@ struct GetDim<std::vector<T>> {
|
||||
};
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
template <class T>
|
||||
class PrimValue {
|
||||
private:
|
||||
@@ -786,6 +790,7 @@ class PrimValue<std::vector<std::vector<std::vector<T>>>> {
|
||||
};
|
||||
|
||||
// TODO: Privide generic multidimensional array type?
|
||||
#endif
|
||||
|
||||
//
|
||||
// TimeSample datatype
|
||||
@@ -1607,6 +1612,7 @@ struct GPrim {
|
||||
|
||||
std::map<std::string, Property> props;
|
||||
|
||||
bool _valid{true}; // default behavior is valid(allow empty GPrim)
|
||||
|
||||
// child nodes
|
||||
std::vector<GPrim> children;
|
||||
@@ -2501,4 +2507,9 @@ class Value {
|
||||
};
|
||||
#endif
|
||||
|
||||
struct Value {
|
||||
|
||||
nonstd::any value;
|
||||
};
|
||||
|
||||
} // namespace tinyusdz
|
||||
|
||||
29
src/pxr-compat.cc
Normal file
29
src/pxr-compat.cc
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "pxr-compat.hh"
|
||||
|
||||
#include "tinyusdz.hh"
|
||||
|
||||
namespace pxr {
|
||||
|
||||
UsdStageRefPtr UsdStage::Open(const std::string &filepath, InitialLoadSet loadset) {
|
||||
(void)filepath;
|
||||
(void)loadset;
|
||||
|
||||
// TODO:
|
||||
return std::shared_ptr<UsdStage>(nullptr);
|
||||
};
|
||||
|
||||
UsdStageRefPtr UsdStage::CreateNew(const std::string &filepath, InitialLoadSet loadset) {
|
||||
(void)filepath;
|
||||
(void)loadset;
|
||||
|
||||
// TODO:
|
||||
return std::shared_ptr<UsdStage>(nullptr);
|
||||
};
|
||||
|
||||
UsdPrim UsdStage::GetPrimAtPath(const SdfPath &path) {
|
||||
(void)path;
|
||||
|
||||
return UsdPrim();
|
||||
};
|
||||
|
||||
} // namespace pxr
|
||||
120
src/pxr-compat.hh
Normal file
120
src/pxr-compat.hh
Normal file
@@ -0,0 +1,120 @@
|
||||
// SPDX-License-Identifier: Apache 2.0
|
||||
|
||||
//
|
||||
// Experimental pxr USD compatible API
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "prim-types.hh"
|
||||
|
||||
#if defined(PXR_STATIC)
|
||||
#define USD_API
|
||||
#else
|
||||
#if defined(USD_EXRPORTS)
|
||||
#if defined(_WIN32)
|
||||
#if defined(_MSC_VER)
|
||||
#define USD_API __declspec(dllexport)
|
||||
#else
|
||||
// assume gcc or clang
|
||||
#define USD_API __attribute__((dllexport))
|
||||
#endif
|
||||
#else
|
||||
#define USD_API
|
||||
#endif
|
||||
#else // import
|
||||
|
||||
#if defined(_WIN32)
|
||||
#if defined(_MSC_VER)
|
||||
#define USD_API __declspec(dllimport)
|
||||
#else
|
||||
// assume gcc or clang
|
||||
#define USD_API __attribute__((dllimport))
|
||||
#endif
|
||||
#else
|
||||
#define USD_API
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#define PXR_INTERNAL_NS pxr
|
||||
|
||||
namespace pxr {
|
||||
|
||||
//namespace Sdf {
|
||||
|
||||
struct SdfLayer;
|
||||
|
||||
// pxr USD uses special pointer class(shared_ptr + alpha) for Handle, but we simply use shared_ptr.
|
||||
using SdfLayerHandle = std::shared_ptr<SdfLayer>;
|
||||
|
||||
|
||||
//} // namespae Sdf
|
||||
|
||||
//namespace Usd {
|
||||
|
||||
struct UsdStage;
|
||||
|
||||
using UsdStagePtr = std::weak_ptr<UsdStage>;
|
||||
using UsdStageRefPtr = std::shared_ptr<UsdStage>;
|
||||
typedef UsdStagePtr UsdStageWeakPtr;
|
||||
|
||||
// prim could be invalid(empty)
|
||||
struct UsdPrim
|
||||
{
|
||||
UsdPrim() : _prim(nullptr) {}
|
||||
UsdPrim(tinyusdz::GPrim *prim) : _prim(prim) {}
|
||||
|
||||
bool IsValid() const {
|
||||
if (!_prim) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: if (IsConcrete(_type))
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
explicit operator bool() {
|
||||
return IsValid();
|
||||
}
|
||||
|
||||
tinyusdz::GPrim *_prim{nullptr};
|
||||
};
|
||||
|
||||
struct SdfPath
|
||||
{
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct UsdStage
|
||||
{
|
||||
|
||||
enum InitialLoadSet
|
||||
{
|
||||
LoadAll,
|
||||
LoadNone
|
||||
};
|
||||
|
||||
USD_API static UsdStageRefPtr CreateNew(const std::string &filepath, InitialLoadSet = LoadAll);
|
||||
USD_API static UsdStageRefPtr CreateInMemory(InitialLoadSet = LoadAll);
|
||||
|
||||
USD_API static UsdStageRefPtr Open(const std::string &filepath, InitialLoadSet = LoadAll);
|
||||
|
||||
USD_API void Save();
|
||||
USD_API void SaveSessionLayers();
|
||||
|
||||
USD_API bool Export(const std::string &filename, bool addSourceFileComments=true) const;
|
||||
USD_API bool ExportToString(std::string *result, bool addSourceFileComments=true) const;
|
||||
|
||||
// returns invalid(empty) prim if corresponding path does not exit in the stage.
|
||||
USD_API UsdPrim GetPrimAtPath(const SdfPath &path);
|
||||
|
||||
|
||||
};
|
||||
|
||||
//} // namespace Usd;
|
||||
} // namespace pxr
|
||||
@@ -1,9 +1,18 @@
|
||||
set(TEST_TARGET_NAME unit-test-tinyusdz)
|
||||
|
||||
add_executable(${TEST_TARGET_NAME}
|
||||
set(TEST_SOURCES
|
||||
unit-main.cc
|
||||
unit-prim-types.cc
|
||||
)
|
||||
|
||||
if (TINYUSDZ_WITH_PXR_COMPAT_API)
|
||||
list(APPEND TEST_SOURCES unit-pxr-compat-api.cc)
|
||||
endif ()
|
||||
|
||||
add_executable(${TEST_TARGET_NAME}
|
||||
${TEST_SOURCES}
|
||||
)
|
||||
|
||||
add_sanitizers(${TEST_TARGET_NAME})
|
||||
|
||||
if (WIN32)
|
||||
@@ -19,3 +28,8 @@ target_include_directories(${TEST_TARGET_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/src
|
||||
|
||||
set_target_properties(${TEST_TARGET_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
||||
if (TINYUSDZ_WITH_PXR_COMPAT_API)
|
||||
target_compile_definitions(${TEST_TARGET_NAME} PRIVATE "TINYUSDZ_WITH_PXR_COMPAT_API")
|
||||
endif (TINYUSDZ_WITH_OPENSUBDIV)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
|
||||
#include "unit-prim-types.h"
|
||||
|
||||
#if defined(TINYUSDZ_WITH_PXR_COMPAT_API)
|
||||
#include "unit-pxr-compat-api.h"
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
TEST_LIST = {
|
||||
{ "prim_type_test", prim_type_test },
|
||||
#if defined(TINYUSDZ_WITH_PXR_COMPAT_API)
|
||||
{ "pxr_compat_api_test", pxr_compat_api_test },
|
||||
#endif
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
using namespace tinyusdz;
|
||||
|
||||
void prim_type_test(void) {
|
||||
#if 0
|
||||
{
|
||||
PrimValue<float> pf;
|
||||
TEST_CHECK(pf.array_dim() == 0);
|
||||
@@ -30,5 +31,6 @@ void prim_type_test(void) {
|
||||
TEST_CHECK(v.array_dim() == 3);
|
||||
TEST_CHECK(v.type_name() == "float[][][]");
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
19
tests/unit/unit-pxr-compat-api.cc
Normal file
19
tests/unit/unit-pxr-compat-api.cc
Normal file
@@ -0,0 +1,19 @@
|
||||
#define TEST_NO_MAIN
|
||||
#include "acutest.h"
|
||||
|
||||
#include "unit-pxr-compat-api.h"
|
||||
|
||||
#include "pxr-compat.hh"
|
||||
|
||||
using namespace PXR_INTERNAL_NS;
|
||||
|
||||
void pxr_compat_api_test(void) {
|
||||
{
|
||||
auto ref = UsdStage::CreateNew("creat.usda");
|
||||
}
|
||||
|
||||
{
|
||||
auto ref = UsdStage::Open("input.usda");
|
||||
}
|
||||
|
||||
}
|
||||
3
tests/unit/unit-pxr-compat-api.h
Normal file
3
tests/unit/unit-pxr-compat-api.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void pxr_compat_api_test(void);
|
||||
Reference in New Issue
Block a user