Integrating OpenMfx as an host#

For a basic example, here is sdk/examples/cpp/host/main.cpp:

  1#include <OpenMfx/Sdk/Cpp/Host/Host>
  2#include <OpenMfx/Sdk/Cpp/Host/MeshEffect>
  3#include <OpenMfx/Sdk/Cpp/Host/EffectRegistry>
  4#include <OpenMfx/Sdk/Cpp/Host/EffectLibrary>
  5#include <OpenMfx/Sdk/Cpp/Host/MeshProps>
  6#include <OpenMfx/Sdk/Cpp/Host/AttributeProps>
  7
  8#include <exception>
  9#include <iostream>
 10#include <vector>
 11#include <array>
 12#include <stdexcept>
 13
 14/**
 15 * To illustrate this demo, we use an arbitrary mesh structure, here a very
 16 * simple one, that can only handle triangle meshes.
 17 */
 18struct MyMeshStruct {
 19	std::vector<std::array<float, 3>> points;
 20	std::vector<std::array<int, 3>> triangles;
 21	bool isInput = true;
 22};
 23
 24/**
 25 * We subclass the OpenMfx::MfxHost base class in order to tell how to expose
 26 * our custom MyMeshStruct structure as an OpenMfx mesh to the effect, and how
 27 * to read back.
 28 */
 29class MyHost : public OpenMfx::Host {
 30	// Callback triggered at the beginning of meshEffectSuite->inputGetMesh()
 31	OfxStatus BeforeMeshGet(OpenMfx::Mesh* mfxMesh) override;
 32	// Callback triggered at the beginning of meshEffectSuite->inputReleaseMesh()
 33	OfxStatus BeforeMeshRelease(OpenMfx::Mesh* mfxMesh) override;
 34	// (The callbacks are defined bellow, after the main function)
 35};
 36
 37// Just get an hardcoded mesh for the sake of the example.
 38const MyMeshStruct& getSomeMesh();
 39
 40void run(const char* filepath) {
 41	// We first create the main host object. It must outlive any other OpenMfx
 42	// related operation so it is usually a singleton used throughout the whole
 43	// application.
 44	MyHost host;
 45	// (Optional: We alias some host functions to be able to easily use the
 46	// OpenMfx API from within this function.)
 47	const auto& propertySuite = host.propertySuite;
 48
 49	// We then get the global registry. This is a singleton that avoid loading
 50	// multiple times the same EffectLibrary when multiple parts of the program
 51	// want to use effects from the same .ofx file.
 52	auto& registry = OpenMfx::EffectRegistry::GetInstance();
 53	// Before using the registry, we must connect it to our host.
 54	registry.setHost(&host);
 55
 56	// We can now load an effect library (or retrieve it if it was already loaded)
 57	// if the file does not exist, or is not an OpenMfx library, this returns
 58	// a null pointer.
 59	// NB: It is important to call releaseLibrary once we no longer use it.
 60	OpenMfx::EffectLibrary* library = registry.getLibrary(filepath);
 61	if (nullptr == library) {
 62		throw std::runtime_error("Could not load binary!");
 63	}
 64
 65	// An OpenFX library may contain both Mesh effects and Image effects, so it is
 66	// possible that effectCount (the number of Mesh effects found) is zero.
 67	if (library->effectCount() == 0) {
 68		throw std::runtime_error("No Mesh Effect found in library!");
 69	}
 70
 71	// At this point, the effect's load() action has still not been called, but
 72	// we can list its identifier and version number. This can be interesting to
 73	// quickly list all available effects at startup.
 74	std::cout << "Using plugin '" << library->effectIdentifier(0) << "'" << std::endl;
 75
 76	// The effect registry handles the loading and description of the effect for us
 77	// (and remembers it from one use of the effect to another one, there is no
 78	// need to call the describe action more than once for the whole application).
 79	OpenMfx::MeshEffect* effectDescriptor = registry.getEffectDescriptor(library, 0);
 80	if (!effectDescriptor) {
 81		throw std::runtime_error("Could not load effect!");
 82	}
 83
 84	// For each use of the effect, we instantiate it. An instance is allowed to
 85	// retain "memory" from previous calls to the main cook function, which is why
 86	// you may want to use multiple instances of the same effect. It also stores
 87	// the value of its input meshes and parameters.
 88	OpenMfx::MeshEffect* effectInstance;
 89	if (!host.CreateInstance(effectDescriptor, effectInstance)) {
 90		throw std::runtime_error("Could not create instance!");
 91	}
 92
 93	// Create the mesh structures for input/output data
 94	// We mark them with a boolean for BeforeMeshGet to know which one is which.
 95	MyMeshStruct inputMeshData = getSomeMesh();
 96	inputMeshData.isInput = true;
 97	MyMeshStruct outputMeshData;
 98	outputMeshData.isInput = false;
 99
100	// Set the inputs meshes of the effect instance
101	// NB: the structure where the output mesh is stored is also considered as an
102	// "input" in OpenMfx' vocable (maybe not the best idea ever but it behaves so
103	// similarily all the time there was no clear reason to give it a different
104	// name and duplicate all the functions). An effect has at most one output,
105	// which is always called kOfxMeshMainOutput.
106	int inputCount = effectInstance->inputs.count();
107	std::cout << "Found " << inputCount << " inputs:" << std::endl;
108	for (int i = 0; i < inputCount; ++i) {
109		auto& input = effectInstance->inputs[i];
110		std::cout << " - " << input.name() << std::endl;
111		if (input.name() != kOfxMeshMainOutput) {
112			// We provide a pointer to our own data structure but do convert to OpenMfx
113			// yet because inputGetMesh might not be called. We will do it in
114			// BeforeMeshGet instead.
115			propertySuite->propSetPointer(&input.mesh.properties, kOfxMeshPropInternalData, 0, (void*)&inputMeshData);
116		}
117		else {
118			propertySuite->propSetPointer(&input.mesh.properties, kOfxMeshPropInternalData, 0, (void*)&outputMeshData);
119		}
120	}
121
122	// Set the parameters of the effect instance
123	int paramCount = effectInstance->parameters.count();
124	std::cout << "Found " << paramCount << " parameters:" << std::endl;
125	for (int i = 0; i < paramCount; ++i) {
126		const auto& param = effectInstance->parameters[i];
127		std::cout << " - " << param.name << std::endl;
128	}
129
130	// Call the main function, which computes the output of the effect, given its
131	// input mesh(es) and parameters. The cook action can be called many times.
132	host.Cook(effectInstance);
133
134	std::cout << "Output mesh has:" << std::endl;
135	std::cout << " - " << outputMeshData.points.size() << " points" << std::endl;
136	std::cout << " - " << outputMeshData.triangles.size() << " triangles" << std::endl;
137
138	// Once done with the instance, we must destroy it.
139	host.DestroyInstance(effectInstance);
140
141	// Release the library, this means we will no longer use the effectDescriptor
142	// and if the registry figures out that nobody is using the library any more,
143	// the descriptor is destroyed and the library unloaded.
144	registry.releaseLibrary(library);
145}
146
147int main(int argc, char** argv) {
148	const char* filepath = "../../c/plugins/Debug/OpenMfx_Example_C_Plugin_mirror.ofx";
149	if (argc > 1) filepath = argv[1];
150
151	try {
152		run(filepath);
153	}
154	catch (const std::runtime_error& err) {
155		std::cerr << err.what() << std::endl;
156		return EXIT_FAILURE;
157	}
158	return EXIT_SUCCESS;
159}
160
161
162OfxStatus MyHost::BeforeMeshGet(OpenMfx::Mesh* mfxMesh) {
163	MyMeshStruct* myMesh = nullptr;
164	propertySuite->propGetPointer(&mfxMesh->properties, kOfxMeshPropInternalData, 0, (void**)&myMesh);
165
166	if (myMesh == nullptr)
167		return kOfxStatErrFatal;
168
169	if (!myMesh->isInput)
170		return kOfxStatOK;
171
172	OpenMfx::MeshProps props;
173	props.pointCount = static_cast<int>(myMesh->points.size());
174	props.cornerCount = 3 * static_cast<int>(myMesh->triangles.size());
175	props.faceCount = static_cast<int>(myMesh->triangles.size());
176	props.constantFaceSize = 3;
177	props.noLooseEdge = true;
178	props.setProperties(propertySuite , &mfxMesh->properties);
179
180	// Convert to attributes
181	int attributeCount = mfxMesh->attributes.count();
182	for (int i = 0; i < attributeCount; ++i) {
183		auto& attribute = mfxMesh->attributes[i];
184		if (attribute.attachment() == OpenMfx::AttributeAttachment::Point && attribute.name() == kOfxMeshAttribPointPosition) {
185			attribute.setComponentCount(3);
186			attribute.setType(OpenMfx::AttributeType::Float);
187			attribute.setData((void*)myMesh->points.data());
188			attribute.setByteStride(3 * sizeof(float));
189		}
190		else if (attribute.attachment() == OpenMfx::AttributeAttachment::Corner && attribute.name() == kOfxMeshAttribCornerPoint) {
191			attribute.setComponentCount(1);
192			attribute.setType(OpenMfx::AttributeType::Int);
193			attribute.setData((void*)myMesh->triangles.data());
194			attribute.setByteStride(3 * sizeof(float));
195		}
196		else if (attribute.attachment() == OpenMfx::AttributeAttachment::Face && attribute.name() == kOfxMeshAttribFaceSize) {
197			attribute.setComponentCount(1);
198			attribute.setType(OpenMfx::AttributeType::Int);
199			attribute.setData(nullptr); // we use kOfxMeshPropConstantFaceSize instead
200			attribute.setByteStride(0);
201		}
202	}
203
204	return kOfxStatOK;
205}
206
207OfxStatus MyHost::BeforeMeshRelease(OpenMfx::Mesh* mfxMesh) {
208	MyMeshStruct* myMesh = nullptr;
209	propertySuite->propGetPointer(&mfxMesh->properties, kOfxMeshPropInternalData, 0, (void**)&myMesh);
210
211	if (myMesh == nullptr)
212		return kOfxStatErrFatal;
213
214	if (myMesh->isInput)
215		return kOfxStatOK;
216
217	OpenMfx::MeshProps props;
218	props.fetchProperties(propertySuite, &mfxMesh->properties);
219
220	if (props.constantFaceSize != 3)
221		return kOfxStatErrUnsupported;
222
223	myMesh->points.resize(props.pointCount);
224	myMesh->triangles.resize(props.faceCount);
225
226	// Convert from attributes
227	int attributeCount = mfxMesh->attributes.count();
228	OpenMfx::AttributeProps attributeProps;
229	for (int i = 0; i < attributeCount; ++i) {
230		auto& attribute = mfxMesh->attributes[i];
231		attributeProps.fetchProperties(propertySuite, &attribute.properties);
232		if (attribute.attachment() == OpenMfx::AttributeAttachment::Point && attribute.name() == kOfxMeshAttribPointPosition) {
233			if (attributeProps.type != OpenMfx::AttributeType::Float)
234				return kOfxStatErrUnsupported;
235			for (int j = 0; j < props.pointCount; ++j) {
236				float* P = attributeProps.at<float>(j);
237				for (int k = 0; k < 3; ++k) {
238					myMesh->points[j][k] = P[k];
239				}
240			}
241		}
242		else if (attribute.attachment() == OpenMfx::AttributeAttachment::Corner && attribute.name() == kOfxMeshAttribCornerPoint) {
243			if (attributeProps.type != OpenMfx::AttributeType::Int)
244				return kOfxStatErrUnsupported;
245			for (int j = 0; j < props.faceCount; ++j) {
246				for (int k = 0; k < 3; ++k) {
247					int C = *attributeProps.at<int>(3 * j + k);
248					myMesh->triangles[j][k] = C;
249				}
250			}
251		}
252	}
253
254	return kOfxStatOK;
255}
256
257const MyMeshStruct& getSomeMesh() {
258	static MyMeshStruct inputMeshData = {
259		{
260			{-1.0, -1.0, -1.0},
261			{1.0, -1.0, -1.0},
262			{-1.0, 1.0, -1.0},
263			{1.0, 1.0, -1.0},
264			{-1.0, -1.0, 1.0},
265			{1.0, -1.0, 1.0},
266			{-1.0, 1.0, 1.0},
267			{1.0, 1.0, 1.0},
268		},
269		{
270			{0, 2, 3}, {0, 3, 1},
271			{4, 5, 7}, {4, 7, 6},
272			{0, 1, 5}, {0, 5, 4},
273			{1, 3, 7}, {1, 7, 5},
274			{3, 2, 6}, {3, 6, 7},
275			{2, 0, 4}, {2, 4, 6},
276		},
277	};
278	return inputMeshData;
279}