Skip to content

Instantly share code, notes, and snippets.

@gareth-morgan-autodesk
Created October 15, 2023 01:15
Show Gist options
  • Save gareth-morgan-autodesk/704364c359a7a0c7ee9280feebd0b24e to your computer and use it in GitHub Desktop.
Save gareth-morgan-autodesk/704364c359a7a0c7ee9280feebd0b24e to your computer and use it in GitHub Desktop.
MaterialX Performance Test
#include <MaterialXRenderGlsl/External/Glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <string>
#include <MaterialXGenGlsl/GlslShaderGenerator.h>
#include <MaterialXRenderGlsl/GlslRenderer.h>
#include <MaterialXRenderGlsl/GLTextureHandler.h>
#include <MaterialXRenderGlsl/TextureBaker.h>
#include <MaterialXRender/CgltfLoader.h>
#include <MaterialXRender/TinyObjLoader.h>
#include <MaterialXRender/GeometryHandler.h>
#include <MaterialXRender/StbImageLoader.h>
#include <MaterialXRenderGlsl/GlslMaterial.h>
#include <MaterialXFormat/Util.h>
#include <chrono>
namespace mx = MaterialX;
void PrintOpenGLErrors(char const* const Function, char const* const File, int const Line)
{
bool Succeeded = true;
GLenum Error = glGetError();
if (Error != GL_NO_ERROR)
{
std::cerr << ("OpenGL Error in %s at line %d calling function %s: '%d 0x%X'", File, Line, Function, Error, Error) << std::endl;
}
}
#define CheckedGLCall(x) do { PrintOpenGLErrors(">>BEFORE<< "#x, __FILE__, __LINE__); (x); PrintOpenGLErrors(#x, __FILE__, __LINE__); } while (0)
#define CheckedGLResult(x) (x); PrintOpenGLErrors(#x, __FILE__, __LINE__);
#define CheckExistingErrors(x) PrintOpenGLErrors(">>BEFORE<< "#x, __FILE__, __LINE__);
const mx::Vector3 DEFAULT_EYE_POSITION(0.0f, 0.0f, 400.0f);
const mx::Vector3 DEFAULT_TARGET_POSITION(0.0f, 0.0f, 0.0f);
const mx::Vector3 DEFAULT_UP_VECTOR(0.0f, 1.0f, 0.0f);
const float DEFAULT_FIELD_OF_VIEW = 45.0f;
const float DEFAULT_NEAR_PLANE = 0.5f;
const float DEFAULT_FAR_PLANE = 10000.0f;
const float PI = std::acos(-1.0f);
int main(int argc, char* argv[])
{
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " numMaterials [-combineMtlxDocs] [-invalidateCache]" << std::endl;
exit(-1);
}
// Number of materials is first argument.
int numMaterials = atoi(argv[1]);
// Combine materials into single document.
bool combineMtlxDocuments = false;
// Create a new name each run to invalidate GPU shader cache.
bool invalidateCache = false;
// Parse flags
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "-combineMtlxDocs") == 0)
combineMtlxDocuments = true;
else if (strcmp(argv[i], "-invalidateCache") == 0)
invalidateCache = true;
}
// Init GLFW.
GLFWwindow* window;
if (!glfwInit())
return -1;
// Create Window.
window = glfwCreateWindow(1280, 1080, "Hello World", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
// Initialize GLAD.
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
exit(-1);
// Setup geometry handler and loaders.
mx::TinyObjLoaderPtr objLoader = mx::TinyObjLoader::create();
mx::CgltfLoaderPtr gltfLoader = mx::CgltfLoader::create();
auto geometryHandler = mx::GeometryHandler::create();
geometryHandler->addLoader(objLoader);
geometryHandler->addLoader(gltfLoader);
geometryHandler->loadGeometry("C:/ADSK/InventorMtlX/resources/Geometry/adsk_shaderball.obj");
// Load standard libraries.
auto stdLib = mx::createDocument();
auto xincludeFiles = mx::loadLibraries({ "libraries" }, { "." }, stdLib);
mx::GenContext genContext = mx::GlslShaderGenerator::create();
// Load environment light.
std::string lightRigFilename = "C:/ADSK/InventorMtlX/resources/Lights/san_giuseppe_bridge_split.mtlx";
mx::DocumentPtr lightRigDoc = mx::createDocument();
mx::readFromXmlFile(lightRigDoc, lightRigFilename);
// Create a default document to import environment light document.
std::string filename = "C:/ADSK/testart/materialx/Simple.mtlx";
mx::DocumentPtr doc = mx::createDocument();
mx::readFromXmlFile(doc, filename);
doc->importLibrary(stdLib);
doc->importLibrary(lightRigDoc);
// Setup lights.
auto lightHandler = mx::LightHandler::create();
std::vector<mx::NodePtr> lights;
lightHandler->findLights(doc, lights);
lightHandler->registerLights(doc, lights, genContext);
lightHandler->setLightSources(lights);
// Array of materials, nodes, and documents.
std::vector<mx::MaterialPtr> materials;
std::vector<mx::NodePtr> materialNodes;
std::vector<mx::DocumentPtr> documents;
std::vector<mx::TypedElementPtr> documentElements;
std::vector<std::string> documentXml;
// Seed rand so colors deterministic.
srand(13);
// Print parameters.
std::cout << "Creating " << numMaterials << " materials";
if (combineMtlxDocuments)
std::cout << " combined int single MTLX document";
if (invalidateCache)
std::cout << " invalidating GPU cache";
std::cout << std::endl;
// XML header and footer for document.
const char* mtlxHeader = R""""(
<?xml version="1.0"?>
<materialx version="1.38" colorspace="lin_rec709">
)"""";
const char* mtlxFooter = R""""(
</materialx>
)"""";
// Begin combined MtlX XML with header.
std::string combinedDocumentXml = mtlxHeader;
// Unique ID for this run (used to invalidate GPU driver cache>)
auto uniqueId = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now().time_since_epoch());
// MaterialX template used by all materials.
const char* mtlxTemplate = R""""(
<standard_surface name="%s_StandardSurfaceShader" type="surfaceshader">
<input name="base" type="float" value="1" />
<input name="metalness" type="float" value="0.05" />
<input name="base_color" type="color3" value="%f,%f,%f" colorspace="lin_rec709" />
<input name="specular" type="float" value="0.1" />
<input name="specular_roughness" type="float" value="0.5" />
<input name="specular_IOR" type="float" value="1.5" />
<input name="thin_walled" type="boolean" value="false" />
</standard_surface>
<surfacematerial name="%s" type="material">
<input name="surfaceshader" type="surfaceshader" nodename="%s_StandardSurfaceShader" />
</surfacematerial>
)"""";
// Create XML documents.s
for (int i = 0; i < numMaterials; i++) {
// Create buffer for document.
std::string mtlxString;
mtlxString.resize(64 * 1000);
// Create new random base color.
float r = float(rand()) / float(RAND_MAX);
float g = float(rand()) / float(RAND_MAX);
float b = float(rand()) / float(RAND_MAX);
// Create unique material name.
std::string name = "Material";
if(combineMtlxDocuments)
name+="_" + std::to_string(i);
if (invalidateCache)
name += "_" + std::to_string(uniqueId.count());
// Build the materialX XML for this material from the template.
sprintf_s(&mtlxString[0],mtlxString.size(), mtlxTemplate, name.c_str(), r, g, b, name.c_str(), name.c_str());
// Remove extra null characters.
mtlxString.erase(std::find(mtlxString.begin(), mtlxString.end(), '\0'), mtlxString.end());
//Add to the documents (with header and footer).
documentXml.push_back(mtlxHeader + mtlxString+ mtlxFooter);
// Add XML to the combined document.
combinedDocumentXml += mtlxString;
}
// Add footer to combined document.
combinedDocumentXml += mtlxFooter;
// Start timing document creation.
auto start_time = std::chrono::high_resolution_clock::now();
// Combined document containing all materials.
mx::DocumentPtr combinedDocument;
if (combineMtlxDocuments)
{
// If using combined document just create one document from combined XML.
combinedDocument = mx::createDocument();
mx::readFromXmlString(combinedDocument, combinedDocumentXml);
for (int i = 0; i < numMaterials; i++) {
documents.push_back(combinedDocument);
}
}
else
{
// If using individual documents, parse each one.
for (int i = 0; i < numMaterials; i++) {
mx::DocumentPtr mtlDoc = mx::createDocument();
mx::readFromXmlString(mtlDoc, documentXml[i]);
documents.push_back(mtlDoc);
}
}
// Finishing timing and print duration.
auto t1 = std::chrono::high_resolution_clock::now();
std::cout << "Document creation took " << std::chrono::duration_cast<std::chrono::milliseconds>(t1 - start_time).count() << " ms" << std::endl;
// Start timing library import.
auto it0 = std::chrono::high_resolution_clock::now();
if (combineMtlxDocuments)
{
// If using combined document import standard library for single document.
combinedDocument->importLibrary(stdLib);
}
else
{
// If using individual document import standard library for each one.
for (int i = 0; i < numMaterials; i++) {
mx::DocumentPtr mtlDoc = documents[i];
mtlDoc->importLibrary(stdLib);
}
}
// Finish timing and print duration.
auto it1 = std::chrono::high_resolution_clock::now();
std::cout << "Importing libraries took " << std::chrono::duration_cast<std::chrono::milliseconds>(it1 - it0).count() << " ms" << std::endl;
// Start timing element lookup.
auto t2 = std::chrono::high_resolution_clock::now();
if (combineMtlxDocuments)
{
// Look up a renderable element for each material from the combined document.
std::vector<mx::TypedElementPtr> elems = mx::findRenderableElements(combinedDocument);
for (mx::TypedElementPtr elem : elems)
{
mx::TypedElementPtr renderableElem = elem;
mx::NodePtr node = elem->asA<mx::Node>();
if (node->getType() == mx::MATERIAL_TYPE_STRING)
{
std::string path = renderableElem->getNamePath();
mx::ElementPtr elem = combinedDocument->getDescendant(path);
mx::TypedElementPtr typedElem = elem ? elem->asA<mx::TypedElement>() : nullptr;
documentElements.push_back(typedElem);
materialNodes.push_back(node);
}
}
if (materialNodes.size() != numMaterials) {
std::cerr << "Element count mismatch" << std::endl;
exit(-1);
}
}
else
{
// Find first renderable element in every document.
for (int i = 0; i < numMaterials; i++) {
mx::NodePtr materialNode;
std::string path;
std::vector<mx::TypedElementPtr> elems = mx::findRenderableElements(documents[i]);
for (mx::TypedElementPtr elem : elems)
{
mx::TypedElementPtr renderableElem = elem;
mx::NodePtr node = elem->asA<mx::Node>();
if (node->getType() == mx::MATERIAL_TYPE_STRING)
{
materialNode = node;
path = renderableElem->getNamePath();
break;
}
}
mx::ElementPtr elem = documents[i]->getDescendant(path);
mx::TypedElementPtr typedElem = elem ? elem->asA<mx::TypedElement>() : nullptr;
documentElements.push_back(typedElem);
materialNodes.push_back(materialNode);
}
}
// Finish timing and print duration.
auto t3 = std::chrono::high_resolution_clock::now();
std::cout << "Element lookup took " << std::chrono::duration_cast<std::chrono::milliseconds>(t3 - t2).count() << " ms" << std::endl;
// Start timing material creation.
auto t4 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < numMaterials; i++) {
mx::MaterialPtr pMtl = mx::GlslMaterial::create();
try {
// Create material from document, material node and element.
pMtl->setDocument(documents[i]);
pMtl->setElement(documentElements[i]);
pMtl->setMaterialNode(materialNodes[i]);
// Generate shader.
pMtl->generateShader(genContext);
}
catch (MaterialX_v1_38_8::ExceptionShaderGenError err) {
std::cerr << err.what() << std::endl;
exit(-1);
}
materials.push_back(pMtl);
}
auto t5 = std::chrono::high_resolution_clock::now();
std::cout << "Material creation took " << std::chrono::duration_cast<std::chrono::milliseconds>(t5 - t4).count() << " ms" << std::endl;
auto t6 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < numMaterials; i++) {
auto pMtl = materials[i];
pMtl->bindShader();
}
// Finish timing and print duration.
auto t7 = std::chrono::high_resolution_clock::now();
std::cout << "Material compilation took " << std::chrono::duration_cast<std::chrono::milliseconds>(t7 - t6).count() << " ms" << std::endl;
// Create view camera.
auto viewCamera = mx::Camera::create();
// Create image handler.
auto imageHandler = mx::GLTextureHandler::create(mx::StbImageLoader::create());
// Create default shadow state.
mx::ShadowState shadowState;
// Set camera projection and view
float fH = std::tan(DEFAULT_FIELD_OF_VIEW / 360.0f * PI) * DEFAULT_NEAR_PLANE;
float fW = fH * 1.0f;
viewCamera->setViewMatrix(mx::Camera::createViewMatrix(DEFAULT_EYE_POSITION, DEFAULT_TARGET_POSITION, DEFAULT_UP_VECTOR));
viewCamera->setProjectionMatrix(mx::Camera::createPerspectiveMatrix(-fW, fW, -fH, fH, DEFAULT_NEAR_PLANE, DEFAULT_FAR_PLANE));
// Setup GL state.
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LEQUAL);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDisable(GL_CULL_FACE);
glDisable(GL_FRAMEBUFFER_SRGB);
glClearColor(0.2f, 0.2f, 0.5f, 1.0f);
// Is this first frame?
bool firstFrame = true;
// GLFW main loop.
while (!glfwWindowShouldClose(window))
{
// Record start of frame.
auto ft0 = std::chrono::high_resolution_clock::now();
// Clear framebuffer.
CheckedGLCall(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
// Wrap all MtlX calls in try/catch.
try
{
// Start the grid of meshes at top left.
float startX = 400;
float startY = 400;
float startZ = -800;
// Meshes per row/col/slice
int dim = 10;
// Space between meshes.
float step = -80.f;
// Current position in row/col/slice.
int nx = 0;
int ny = 0;
int nz = 0;
// Render each mesh with its own material.
for (int i = 0; i < numMaterials; i++) {
// Mesh postiion.
float x = startX + nx * step;
float y = startY + ny * step;
float z = startZ + nz * step;
// Set mesh position as world matrix.
viewCamera->setWorldMatrix(mx::Matrix44::createTranslation(mx::Vector3(x,y,z)));
// If at the end of a row go to next one.
if (nx == dim - 1) {
nx = 0;
// If at end of slice got to next one.
if (ny == dim - 1) {
ny = 0;
nz++;
}
else ny++; // Otherwise go to next row
}
else nx++; // Otherwise go to next column.
// Get the material.
auto pMtl = materials[i];
// Bind view matrices to material shader.
pMtl->bindViewInformation(viewCamera);
// Bind lighting to material shader.
pMtl->bindLighting(lightHandler, imageHandler, shadowState);
// Bind shader.
pMtl->bindShader();
// Bind images (should not be any)
pMtl->bindImages(imageHandler, { "" });
// Render all the meshes for this material.
for (auto mesh : geometryHandler->getMeshes())
{
// Bind mesh geometry buffers to material.
pMtl->bindMesh(mesh);
// Render all partitions in mesh.
for (size_t partIndex = 0; partIndex < mesh->getPartitionCount(); partIndex++)
{
// Draw the partion with this material.
mx::MeshPartitionPtr geom = mesh->getPartition(partIndex);
pMtl->drawPartition(geom);
}
}
// Unbind images.
pMtl->unbindImages(imageHandler);
// Check for GL errors.
CheckExistingErrors("MaterialX");
}
// If first frame, then dump frame time.
auto ft1 = std::chrono::high_resolution_clock::now();
if (firstFrame)
{
std::cout << "First frame render took " << std::chrono::duration_cast<std::chrono::milliseconds>(ft1 - ft0).count() << " ms" << std::endl;
firstFrame = false;
}
}
// Catch Mtlx render errors.
catch (MaterialX_v1_38_8::ExceptionRenderError err) {
std::cerr << err.what() << std::endl;
for (auto errStr : err.errorLog())
std::cerr << errStr << std::endl;
exit(-1);
}
// Finish GLFW frame.
glfwSwapBuffers(window);
glfwPollEvents();
}
// Shutdown GLFW.
glfwTerminate();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment