Created
October 15, 2023 01:15
-
-
Save gareth-morgan-autodesk/704364c359a7a0c7ee9280feebd0b24e to your computer and use it in GitHub Desktop.
MaterialX Performance Test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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