/* This file is part of Mitsuba, a physically based rendering system. Copyright (c) 2007-2012 by Wenzel Jakob and others. Mitsuba is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License Version 3 as published by the Free Software Foundation. Mitsuba is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #if defined(WIN32) #include #endif MTS_NAMESPACE_BEGIN class KDBench : public Utility { public: void help() { cout << endl; cout << "Synopsis: kd-tree performance benchmark. Traces uniformly distributed rays" << endl; cout << "though the bounding sphere of a scene and reports the resulting number of" << endl; cout << "rays per second. The main intent of this utility is to optimize the kd-tree" << endl; cout << "construction parameters for particular scenes and machines." << endl; cout << endl; cout << "Usage: mtsutil kdbench [options] " << endl; cout << "Options/Arguments:" << endl; cout << " -h Display this help text" << endl << endl; cout << " -t value Specify the SAH traversal cost" << endl << endl; cout << " -i value Specify the SAH intersection cost" << endl << endl; cout << " -e value Specify the SAH empty space bonus" << endl << endl; cout << " -b value Specify the number of min-max bins" << endl << endl; cout << " -c true/false Enable/disable primitive clipping (aka. \"perfect splits\")" << endl << endl; cout << " -p true/false Enable/disable parallel tree construction" << endl << endl; cout << " -r true/false Enable/disable retraction of bad splits" << endl << endl; cout << " -l value Specify the primitive count, below which a leaf node" << endl; cout << " will always be created" << endl << endl; cout << " -d depth Specify the maximum tree depth" << endl << endl; cout << " -x value Specify the number of primitives, at which the " << endl; cout << " builder will switch from (approximate) Min-Max " << endl; cout << " binning to the more accurate O(n log n) SAH-based " << endl; cout << " optimization method." << endl << endl; cout << " -f Try to empirically find the best SAH cost values by" << endl; cout << " fitting the cost model to collected performance data" << endl << endl; cout << "Examples:" << endl; cout << " E.g. to build a tree for the Stanford bunny having a low SAH cost, type " << endl << endl; cout << " $ mtsutil kdbench -e .9 -l1 -d48 -x100000 data/tests/bunny.ply" << endl << endl; cout << " To get SAH costs comparable to [Wald and Havran 06], also specify -t15 -i20" << endl << endl; cout << " The high -x paramer effectively disables Min-Max binning, which " << endl; cout << " leads to a slower and more memory-intensive build, so don't try" << endl; cout << " this on a huge model." << endl << endl; } int run(int argc, char **argv) { ref fileResolver = Thread::getThread()->getFileResolver(); int optchar; char *end_ptr = NULL; Float intersectionCost = -1, traversalCost = -1, emptySpaceBonus = -1; int stopPrims = -1, maxDepth = -1, exactPrims = -1, minMaxBins = -1; bool clip = true, parallel = true, retract = true, fitParameters = false; optind = 1; /* Parse command-line arguments */ while ((optchar = getopt(argc, argv, "i:t:e:c:p:r:l:x:b:d:hf")) != -1) { switch (optchar) { case 'h': { help(); return 0; } break; case 'f': fitParameters = true; break; case 'i': intersectionCost = (Float) strtod(optarg, &end_ptr); if (*end_ptr != '\0') SLog(EError, "Could not parse the intersection cost!"); break; case 't': traversalCost = (Float) strtod(optarg, &end_ptr); if (*end_ptr != '\0') SLog(EError, "Could not parse the traversal cost!"); break; case 'e': emptySpaceBonus = (Float) strtod(optarg, &end_ptr); if (*end_ptr != '\0') SLog(EError, "Could not parse the empty space bonus!"); break; case 'l': stopPrims = strtol(optarg, &end_ptr, 10); if (*end_ptr != '\0') SLog(EError, "Could not parse the stopping primitive count!"); break; case 'd': maxDepth = strtol(optarg, &end_ptr, 10); if (*end_ptr != '\0') SLog(EError, "Could not parse the maximum depth!"); break; case 'x': exactPrims = strtol(optarg, &end_ptr, 10); if (*end_ptr != '\0') SLog(EError, "Could not parse the -e parameter!"); break; case 'b': minMaxBins = strtol(optarg, &end_ptr, 10); if (*end_ptr != '\0') SLog(EError, "Could not parse the min-max bins parameter!"); break; case 'c': if (strcmp(optarg, "true") == 0) clip = true; else if (strcmp(optarg, "false") == 0) clip = false; else SLog(EError, "Could not parse the clipping parameter!"); break; case 'p': if (strcmp(optarg, "true") == 0) parallel = true; else if (strcmp(optarg, "false") == 0) parallel = false; else SLog(EError, "Could not parse the parallel build parameter!"); break; case 'r': if (strcmp(optarg, "true") == 0) retract = true; else if (strcmp(optarg, "false") == 0) retract = false; else SLog(EError, "Could not parse the retraction parameter!"); break; }; } if (optind == argc || optind+1 < argc) { help(); return 0; } ref scene; ref kdtree; std::string lowercase = boost::to_lower_copy(std::string(argv[optind])); if (boost::ends_with(lowercase, ".xml")) { fs::path filename = fileResolver->resolve(argv[optind]), filePath = fs::absolute(filename).parent_path(), baseName = filename.stem(); ref frClone = fileResolver->clone(); frClone->prependPath(filePath); Thread::getThread()->setFileResolver(frClone); scene = loadScene(argv[optind]); kdtree = scene->getKDTree(); } else if (boost::ends_with(lowercase, ".ply")) { Properties props("ply"); props.setString("filename", argv[optind]); ref mesh; mesh = static_cast (PluginManager::getInstance()-> createObject(MTS_CLASS(TriMesh), props)); mesh->configure(); kdtree = new ShapeKDTree(); kdtree->addShape(mesh); } else { Log(EError, "The supplied scene filename must end in either PLY or XML!"); } if (intersectionCost != -1) kdtree->setQueryCost(intersectionCost); if (traversalCost != -1) kdtree->setTraversalCost(traversalCost); if (emptySpaceBonus != -1) kdtree->setEmptySpaceBonus(emptySpaceBonus); if (stopPrims != -1) kdtree->setStopPrims(stopPrims); if (maxDepth != -1) kdtree->setMaxDepth(maxDepth); if (exactPrims != -1) kdtree->setExactPrimitiveThreshold(exactPrims); if (minMaxBins != -1) kdtree->setMinMaxBins(minMaxBins); kdtree->setClip(clip); kdtree->setRetract(retract); kdtree->setParallelBuild(parallel); /* Show some statistics, and make sure it roughly fits in 80cols */ Logger *logger = Thread::getThread()->getLogger(); DefaultFormatter *formatter = ((DefaultFormatter *) logger->getFormatter()); logger->setLogLevel(EDebug); formatter->setHaveDate(false); if (scene) scene->initialize(); else kdtree->build(); BSphere bsphere(kdtree->getAABB().getBSphere()); const size_t nRays = 5000000; if (!fitParameters) { Log(EInfo, "Bounding sphere: %s", bsphere.toString().c_str()); Float best = 0; for (int j=0; j<3; ++j) { ref random = new Random(); ref timer = new Timer(); size_t nIntersections = 0; Log(EInfo, "Shooting " SIZE_T_FMT " rays (1 thread, incoherent) ..", nRays); for (size_t i=0; inextFloat(), random->nextFloat()), sample2(random->nextFloat(), random->nextFloat()); Point p1 = bsphere.center + Warp::squareToUniformSphere(sample1) * bsphere.radius; Point p2 = bsphere.center + Warp::squareToUniformSphere(sample2) * bsphere.radius; Ray r(p1, normalize(p2-p1), 0.0f); Intersection its; if (kdtree->rayIntersect(r, its)) nIntersections++; } Log(EInfo, "Found " SIZE_T_FMT " intersections in %i ms", nIntersections, timer->getMilliseconds()); Float mrays = nRays / (timer->getMilliseconds() * (Float) 1000); Log(EInfo, "-> %.3f MRays/s", mrays); Log(EInfo, ""); best = std::max(best, mrays); } Log(EInfo, "Best of three: %.3f MRays/s", best); } else { Float intersectionCost, traversalCost; kdtree->findCosts(intersectionCost, traversalCost); } Thread::getThread()->getLogger()->setLogLevel(EInfo); return 0; } MTS_DECLARE_UTILITY() }; MTS_EXPORT_UTILITY(KDBench, "kd-tree performance benchmark") MTS_NAMESPACE_END