diff options
Diffstat (limited to 'desktop/source/app/opencl.cxx')
-rw-r--r-- | desktop/source/app/opencl.cxx | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/desktop/source/app/opencl.cxx b/desktop/source/app/opencl.cxx new file mode 100644 index 0000000000..ad3df6bf3f --- /dev/null +++ b/desktop/source/app/opencl.cxx @@ -0,0 +1,257 @@ +/* -*- 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 <app.hxx> + +#include <config_version.h> +#include <config_feature_opencl.h> +#include <config_folders.h> + +#include <rtl/bootstrap.hxx> +#include <sal/log.hxx> + +#include <officecfg/Office/Calc.hxx> +#include <officecfg/Office/Common.hxx> + +#include <comphelper/propertyvalue.hxx> +#include <svl/documentlockfile.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <com/sun/star/table/XCell2.hpp> +#include <com/sun/star/sheet/XCalculatable.hpp> +#include <com/sun/star/sheet/XSpreadsheet.hpp> +#include <com/sun/star/sheet/XSpreadsheets.hpp> +#include <com/sun/star/sheet/XSpreadsheetDocument.hpp> + +#if HAVE_FEATURE_OPENCL +#include <opencl/openclwrapper.hxx> +#endif +#include <opencl/OpenCLZone.hxx> + +#include <osl/file.hxx> +#include <osl/process.h> + +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("$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/opencltest"); + 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("Hidden", + true) }; + + xComponent.set(xLoader->loadComponentFromURL(rURL, "_blank", 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("=MAX(results)"); + 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("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/opencl/cl-test.ods"); + 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<comphelper::ConfigurationChanges> 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<comphelper::ConfigurationChanges> 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: */ |