/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * This module exists to validate the OpenCL implementation, * where necessary during startup; and before we load or * calculate using OpenCL. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_FEATURE_OPENCL #include #endif #include #include #include using namespace ::osl; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::frame; namespace desktop { #if HAVE_FEATURE_OPENCL static bool testOpenCLDriver() { // A simple OpenCL test run in a separate process in order to test // whether the driver crashes (asserts,etc.) when trying to use OpenCL. SAL_INFO("opencl", "Starting CL driver test"); OUString testerURL(u"$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/opencltest"_ustr); rtl::Bootstrap::expandMacros(testerURL); //TODO: detect failure OUString deviceName, platformName; openclwrapper::getOpenCLDeviceName( deviceName, platformName ); rtl_uString* args[] = { deviceName.pData, platformName.pData }; sal_Int32 numArgs = 2; oslProcess process; oslSecurity security = osl_getCurrentSecurity(); oslProcessError error = osl_executeProcess(testerURL.pData, args, numArgs, osl_Process_SEARCHPATH | osl_Process_HIDDEN, security, nullptr, nullptr, 0, &process ); osl_freeSecurityHandle( security ); if( error != osl_Process_E_None ) { SAL_WARN( "opencl", "failed to start CL driver test: " << error ); return false; } // If the driver takes more than 10 seconds, it's probably broken/useless. TimeValue timeout( 10, 0 ); error = osl_joinProcessWithTimeout( process, &timeout ); if( error == osl_Process_E_None ) { oslProcessInfo info; info.Size = sizeof( info ); error = osl_getProcessInfo( process, osl_Process_EXITCODE, &info ); if( error == osl_Process_E_None ) { if( info.Code == 0 ) { SAL_INFO( "opencl", "CL driver test passed" ); osl_freeProcessHandle( process ); return true; } else { SAL_WARN( "opencl", "CL driver test failed - disabling: " << info.Code ); osl_freeProcessHandle( process ); return false; } } } SAL_WARN( "opencl", "CL driver test did not finish - disabling: " << error ); osl_terminateProcess( process ); osl_freeProcessHandle( process ); return false; } static bool testOpenCLCompute(const Reference< XDesktop2 > &xDesktop, const OUString &rURL) { bool bSuccess = false; css::uno::Reference< css::lang::XComponent > xComponent; sal_uInt64 nKernelFailures = openclwrapper::kernelFailures; SAL_INFO("opencl", "Starting CL test spreadsheet"); // A stale lock file would make the loading fail, so make sure to remove it. try { ::svt::DocumentLockFile lockFile( rURL ); lockFile.RemoveFileDirectly(); } catch (const css::uno::Exception&) { } try { css::uno::Reference< css::frame::XComponentLoader > xLoader(xDesktop, css::uno::UNO_QUERY_THROW); css::uno::Sequence< css::beans::PropertyValue > aArgs{ comphelper::makePropertyValue(u"Hidden"_ustr, true) }; xComponent.set(xLoader->loadComponentFromURL(rURL, u"_blank"_ustr, 0, aArgs)); // What an unpleasant API to use. css::uno::Reference< css::sheet::XCalculatable > xCalculatable( xComponent, css::uno::UNO_QUERY_THROW); css::uno::Reference< css::sheet::XSpreadsheetDocument > xSpreadDoc( xComponent, css::uno::UNO_QUERY_THROW ); css::uno::Reference< css::sheet::XSpreadsheets > xSheets( xSpreadDoc->getSheets(), css::uno::UNO_SET_THROW ); css::uno::Reference< css::container::XIndexAccess > xIndex( xSheets, css::uno::UNO_QUERY_THROW ); css::uno::Reference< css::sheet::XSpreadsheet > xSheet( xIndex->getByIndex(0), css::uno::UNO_QUERY_THROW); // So we insert our MAX call at the end on a named range. css::uno::Reference< css::table::XCell2 > xThresh( xSheet->getCellByPosition(1,1), css::uno::UNO_QUERY_THROW ); // B2 double fThreshold = xThresh->getValue(); // We need pure OCL formulae all the way through the // dependency chain, or we fall-back. xCalculatable->calculateAll(); // So we insert our MAX call at the end on a named range. css::uno::Reference< css::table::XCell2 > xCell( xSheet->getCellByPosition(1,0), css::uno::UNO_QUERY_THROW ); xCell->setFormula(u"=MAX(results)"_ustr); double fResult = xCell->getValue(); // Ensure the maximum variance is below our tolerance. if (fResult > fThreshold) { SAL_WARN("opencl", "OpenCL results unstable - disabling; result: " << fResult << " vs. " << fThreshold); } else { SAL_INFO("opencl", "calculating smoothly; result: " << fResult); bSuccess = true; } } catch (const css::uno::Exception &) { TOOLS_WARN_EXCEPTION("opencl", "OpenCL testing failed - disabling"); } if (nKernelFailures != openclwrapper::kernelFailures) { // tdf#100883 - defeat SEH exception handling fallbacks. SAL_WARN("opencl", "OpenCL kernels failed to compile, " "or took SEH exceptions " << nKernelFailures << " != " << openclwrapper::kernelFailures); bSuccess = false; } if (!bSuccess) OpenCLZone::hardDisable(); if (xComponent.is()) xComponent->dispose(); return bSuccess; } void Desktop::CheckOpenCLCompute(const Reference< XDesktop2 > &xDesktop) { if (!openclwrapper::canUseOpenCL() || Application::IsSafeModeEnabled()) return; SAL_INFO("opencl", "Initiating test of OpenCL device"); OpenCLZone aZone; OpenCLInitialZone aInitialZone; OUString aDevice = officecfg::Office::Calc::Formula::Calculation::OpenCLDevice::get(); OUString aSelectedCLDeviceVersionID; if (!openclwrapper::switchOpenCLDevice( aDevice, officecfg::Office::Calc::Formula::Calculation::OpenCLAutoSelect::get(), false /* bForceEvaluation */, aSelectedCLDeviceVersionID)) { SAL_WARN("opencl", "Failed to initialize OpenCL for test"); OpenCLZone::hardDisable(); return; } // Append our app version as well. aSelectedCLDeviceVersionID += "--" LIBO_VERSION_DOTTED; // Append timestamp of the file. OUString aURL(u"$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/opencl/cl-test.ods"_ustr); rtl::Bootstrap::expandMacros(aURL); DirectoryItem aItem; (void)DirectoryItem::get( aURL, aItem ); FileStatus aFileStatus( osl_FileStatus_Mask_ModifyTime ); (void)aItem.getFileStatus( aFileStatus ); TimeValue aTimeVal = aFileStatus.getModifyTime(); aSelectedCLDeviceVersionID += "--" + OUString::number(aTimeVal.Seconds); if (aSelectedCLDeviceVersionID == officecfg::Office::Common::Misc::SelectedOpenCLDeviceIdentifier::get()) return; // OpenCL device changed - sanity check it and disable if bad. sal_Int32 nOrigMinimumSize = officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::get(); { // set the minimum group size to something small for quick testing. std::shared_ptr xBatch(comphelper::ConfigurationChanges::create()); officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::set(3 /* small */, xBatch); xBatch->commit(); } // Hopefully at least basic functionality always works and broken OpenCL implementations break // only when they are used to compute something. If this assumptions turns out to be not true, // the driver check needs to be moved sooner. bool bSucceeded = testOpenCLDriver() && testOpenCLCompute(xDesktop, aURL); { // restore the minimum group size std::shared_ptr xBatch(comphelper::ConfigurationChanges::create()); officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::set(nOrigMinimumSize, xBatch); officecfg::Office::Common::Misc::SelectedOpenCLDeviceIdentifier::set(aSelectedCLDeviceVersionID, xBatch); xBatch->commit(); } if (!bSucceeded) OpenCLZone::hardDisable(); } #endif // HAVE_FEATURE_OPENCL } // end namespace desktop /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */