// Copyright (c) 2012-2017 VideoStitch SAS // Copyright (c) 2018 stitchEm #include "cmdUtils.hpp" #include <libvideostitch/logging.hpp> #include <libvideostitch/gpu_device.hpp> #include <libvideostitch/inputController.hpp> #include <libvideostitch/panoDef.hpp> #include <libvideostitch/parse.hpp> #include <libvideostitch/status.hpp> #include "libgpudiscovery/backendLibHelper.hpp" // System-dependant filesystem stuff. #ifdef _MSC_VER #include <direct.h> #define chdir _chdir #define snprintf _snprintf #define getcwd _getcwd #include <io.h> #include <sys/types.h> #include <sys/stat.h> #else #include <unistd.h> #endif #ifdef _MSC_VER #include <libgpudiscovery/delayLoad.hpp> #ifdef DELAY_LOAD_ENABLED SET_DELAY_LOAD_HOOK #endif // DELAY_LOAD_ENABLED FARPROC WINAPI delayFailureHook(unsigned dliNotify, PDelayLoadInfo pdli) { switch (dliNotify) { case dliFailLoadLib: throw std::runtime_error(std::string(pdli->szDll) + " could not be loaded"); case dliFailGetProc: throw std::runtime_error("Could not find procedure in module " + std::string(pdli->szDll)); default: assert(false); throw std::runtime_error("Unknown error code"); }; } #endif // _MSC_VER namespace VideoStitch { namespace Cmd { namespace { /** * Give a pointer on the file component of a full input filename. * @param input The input full filename. * @return the filename, belonging to the same char* as @input. */ const char* extractFilename(const char* input) { const char* file = input; #ifdef _MSC_VER // separator is '\' or '/' for (const char* p = input; *p != 0; ++p) { if (*p == '/' || *p == '\\') { file = p + 1; } } #else // separator is '/' for (const char* p = input; *p != 0; ++p) { if (*p == '/') { file = p + 1; } } #endif return file; } } // namespace bool loadGPUBackend(const int deviceId, int& returnCode) { // select backend VideoStitch::Discovery::Framework selectedFramework = VideoStitch::Discovery::Framework::Unknown; Discovery::DeviceProperties prop; prop.supportedFramework = VideoStitch::Discovery::Framework::Unknown; // if a device is selected, select its backend if (deviceId != -1) { if (Discovery::getDeviceProperties(deviceId, prop) && BackendLibHelper::isBackendAvailable(prop.supportedFramework)) { selectedFramework = prop.supportedFramework; std::cout << "Selected device " << deviceId << std::endl; } } // if no device is selected, try select best available backend if (selectedFramework == VideoStitch::Discovery::Framework::Unknown) { std::cout << "No device selected, selecting default device" << std::endl; selectedFramework = BackendLibHelper::getBestFrameworkAndBackend(); } bool needToRestart = false; if (BackendLibHelper::selectBackend(selectedFramework, &needToRestart)) { std::cout << "[videostitch-cmd] " << VideoStitch::Discovery::getFrameworkName(selectedFramework) << " backend selected" << std::endl; } else { std::cerr << "No CUDA nor OpenCL capable GPU detected on your system. " << "A Nvidia card with CUDA capability and CUDA drivers or " << "a graphics card with OpenCL capability and drivers installed are mandatory to run the software. " << std::endl; returnCode = 1; return false; } if (needToRestart) { std::cout << "Closing application to finalize setup " << std::endl; #ifdef __APPLE__ BackendLibHelper::forceUpdateSymlink(); #endif returnCode = 0; return false; } #ifdef DELAY_LOAD_ENABLED // failure hook, to prevent crash on delay load failures PfnDliHook oldFailureHook = __pfnDliFailureHook2; __pfnDliFailureHook2 = &delayFailureHook; try { #endif Status::OK(); #ifdef DELAY_LOAD_ENABLED } catch (std::exception& e) { std::cerr << "Error using backend library: " << e.what() << std::endl; } __pfnDliFailureHook2 = oldFailureHook; #endif return true; } const char* changeWorkingPathToPtvFolder(char* ptvPath) { // Change directory to the project directory, so that all paths are relative. const char* ptvFile = extractFilename(ptvPath); if (ptvFile != ptvPath) { // if ptvPath is "/" if (ptvFile - ptvPath == 1) { if (chdir("/")) { Logger::get(Logger::Error) << "Incorrect input file: " << ptvFile << std::endl; return nullptr; } } else { ptvPath[ptvFile - ptvPath - 1] = 0; // trim the path if (chdir(ptvPath)) { Logger::get(Logger::Error) << "Incorrect input file: " << ptvFile << std::endl; return nullptr; } } } return ptvFile; } bool parseInputPath(int argc, char** argv, int index, char** ptvPath) { if (index >= argc - 1 || (argv[index + 1][0] == '-' && argv[index + 1][1] != '\0')) { Logger::get(Logger::Error) << "The -i option takes a parameter." << std::endl; return false; } if (*ptvPath) { Logger::get(Logger::Error) << "Several input files: \"" << *ptvPath << "\" and \"" << argv[index + 1] << "\"" << std::endl; return false; } *ptvPath = argv[index + 1]; return true; } bool parseOutputPath(int argc, char** argv, int index, char** ptvPath) { if (index >= argc - 1 || (argv[index + 1][0] == '-' && argv[index + 1][1] != '\0')) { Logger::get(Logger::Error) << "The -o option takes a parameter." << std::endl; return false; } if (*ptvPath) { Logger::get(Logger::Error) << "Several output files: \"" << *ptvPath << "\" and \"" << argv[index + 1] << "\"" << std::endl; return false; } *ptvPath = argv[index + 1]; return true; } bool parseFirstFrame(int argc, char** argv, int index, int* firstFrame) { if (index >= argc - 1 || (argv[index + 1][0] == '-' && argv[index + 1][1] != '\0')) { Logger::get(Logger::Error) << "The -f option takes a parameter." << std::endl; return false; } *firstFrame = atoi(argv[index + 1]); return true; } bool parseLastFrame(int argc, char** argv, int index, int* lastFrame) { if (index >= argc - 1 || (argv[index + 1][0] == '-' && argv[index + 1][1] != '\0')) { Logger::get(Logger::Error) << "The -l option takes a parameter." << std::endl; return false; } *lastFrame = atoi(argv[index + 1]); return true; } Status checkDevice(int vsDeviceID) { FAIL_RETURN(GPU::setDefaultBackendDeviceVS(vsDeviceID)); return GPU::checkDefaultBackendDeviceInitialization(); } bool parseDeviceId(int argc, char** argv, int& deviceId, int& returnCode) { deviceId = -1; for (int i = 1; i < argc; ++i) { if (argv[i][0] != '\0' && argv[i][1] != '\0' && argv[i][0] == '-') { switch (argv[i][1]) { case 'd': if (i >= argc - 1 || argv[i + 1][0] == '-') { std::cout << "-d option used without parameter, list compatible GPU devices:" << std::endl; int numDevices = Discovery::getNumberOfDevices(); if (numDevices == 0) { std::cerr << "Could not find any compatible GPU device on this computer." << std::endl; returnCode = 1; return false; } for (int i = 0; i < numDevices; ++i) { Discovery::DeviceProperties prop; if (Discovery::getDeviceProperties(i, prop)) { std::cout << "Device " << i << ": " << prop << std::endl; } else { std::cerr << "Could not query device properties for device " << i << std::endl; } } returnCode = 0; return false; } // else std::stringstream ss(argv[++i]); if (!(ss >> deviceId)) { std::cerr << "Malformed device id: '" << argv[i] << "'" << std::endl; returnCode = 1; return false; } break; } } } return true; } /** * Detects GPU devices and returns true on success. * No check is done for GPU usability */ bool selectGPUDevice(int argc, char** argv, int& deviceId, int& returnCode) { if (!parseDeviceId(argc, argv, deviceId, returnCode)) { return false; } int defaultGpu = 0; int devCount = Discovery::getNumberOfDevices(); if (devCount == 0) { std::cerr << "Error: No GPU found!" << std::endl; returnCode = 1; return false; } else if (devCount == 1) { if (deviceId != -1) { std::cout << "Warning: Only one GPU found, ignoring -d." << std::endl; } deviceId = defaultGpu; } else { if (deviceId == -1) { std::cerr << "Error: More than 1 device and no '-d' option found. Falling back to the default device ('-d 0')." << std::endl; deviceId = defaultGpu; } else { if (deviceId >= devCount) { std::cerr << "Error: No such device " << deviceId << ". Only " << devCount << " GPUs found." << std::endl; returnCode = 1; return false; } } } return true; } /** * @brief Prints properties of a given GPU device. * @param os Output stream to print to. * @param vsGPUDeviceID GPU device (libvideostitch internal ID) */ void printDeviceProperties(ThreadSafeOstream& os, int vsGPUDeviceID) { Discovery::DeviceProperties prop; if (VideoStitch::Discovery::getDeviceProperties(vsGPUDeviceID, prop)) { os << prop << std::endl; } else { os << "Error when trying to access device properties of GPU device #" << vsGPUDeviceID << std::endl; } } /** * Checks the usable GPU devices and returns true on success. */ bool checkGPUDevice(Core::PanoDeviceDefinition& dev) { auto& errLog = Logger::get(Logger::Error); if (!checkDevice(dev.device).ok()) { errLog << "Cannot use device " << dev.device << std::endl; return false; } printDeviceProperties(Logger::get(Logger::Verbose), dev.device); return true; } std::unique_ptr<Core::PanoDefinition> parsePanoDef(const Ptv::Value& ptvRoot, const char* ptvFile) { if (!ptvRoot.has("pano")) { Logger::get(Logger::Error) << "Error: No 'pano' entry in file: " << ptvFile << std::endl; return nullptr; } // Create a runtime panorama from the parsed project. Core::PanoDefinition* panoDef = Core::PanoDefinition::create(*ptvRoot.has("pano")); if (!panoDef) { Logger::get(Logger::Error) << "Error: Invalid panorama definition in " << ptvFile << std::endl; return nullptr; } return std::unique_ptr<Core::PanoDefinition>{panoDef}; } bool parsePtvFile(Ptv::Parser& parser, const char* ptvFile) { // Load the project and parse it. if (!parser.parse(ptvFile)) { Logger::get(Logger::Error) << "Error: Cannot parse PTV file: " << ptvFile << std::endl; Logger::get(Logger::Error) << parser.getErrorMessage() << std::endl; return false; } return true; } std::unique_ptr<Core::PanoDefinition> parsePanoDef(Ptv::Parser& parser, const char* ptvFile) { if (parsePtvFile(parser, ptvFile)) { return parsePanoDef(parser.getRoot(), ptvFile); } return nullptr; } bool normalizeFrameBoundaries(const Core::InputController& controller, const frameid_t firstFrame, frameid_t& lastFrame) { lastFrame = lastFrame < 0 ? controller.getLastStitchableFrame() : std::min(lastFrame, controller.getLastStitchableFrame()); if (lastFrame == NO_LAST_FRAME) { Logger::get(Logger::Error) << "Last frame auto detection was enabled, but all the readers are unbounded (Are you " "using only procedural readers ?)." << std::endl; lastFrame = firstFrame; } if (lastFrame < firstFrame) { Logger::get(Logger::Error) << "Nothing to stitch: last_frame = " << lastFrame << " < first_frame = " << firstFrame << "." << std::endl; return false; } Logger::get(Logger::Info) << "Will stitch " << lastFrame - firstFrame + 1 << " images." << std::endl; return true; } } // namespace Cmd } // namespace VideoStitch