LibreOffice Module desktop (master)  1
opencl.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9 /*
10  * This module exists to validate the OpenCL implementation,
11  * where necessary during startup; and before we load or
12  * calculate using OpenCL.
13  */
14 
15 #include <app.hxx>
16 
17 #include <config_version.h>
18 #include <config_feature_opencl.h>
19 #include <config_folders.h>
20 
21 #include <rtl/bootstrap.hxx>
22 #include <sal/log.hxx>
23 
24 #include <officecfg/Office/Calc.hxx>
25 #include <officecfg/Office/Common.hxx>
26 
27 #include <svl/documentlockfile.hxx>
28 #include <tools/diagnose_ex.h>
29 
30 #include <com/sun/star/table/XCell2.hpp>
31 #include <com/sun/star/sheet/XCalculatable.hpp>
32 #include <com/sun/star/sheet/XSpreadsheet.hpp>
33 #include <com/sun/star/sheet/XSpreadsheets.hpp>
34 #include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
35 
36 #if HAVE_FEATURE_OPENCL
37 #include <opencl/openclwrapper.hxx>
38 #endif
39 #include <opencl/OpenCLZone.hxx>
40 
41 #include <osl/file.hxx>
42 #include <osl/process.h>
43 
44 using namespace ::osl;
45 using namespace ::com::sun::star::uno;
46 using namespace ::com::sun::star::frame;
47 
48 namespace desktop {
49 
50 #if HAVE_FEATURE_OPENCL
51 
52 static bool testOpenCLDriver()
53 {
54  // A simple OpenCL test run in a separate process in order to test
55  // whether the driver crashes (asserts,etc.) when trying to use OpenCL.
56  SAL_INFO("opencl", "Starting CL driver test");
57 
58  OUString testerURL("$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/opencltest");
59  rtl::Bootstrap::expandMacros(testerURL); //TODO: detect failure
60 
61  OUString deviceName, platformName;
62  openclwrapper::getOpenCLDeviceName( deviceName, platformName );
63  rtl_uString* args[] = { deviceName.pData, platformName.pData };
64  sal_Int32 numArgs = 2;
65 
66  oslProcess process;
67  oslSecurity security = osl_getCurrentSecurity();
68  oslProcessError error = osl_executeProcess(testerURL.pData, args, numArgs,
69  osl_Process_SEARCHPATH | osl_Process_HIDDEN, security,
70  nullptr, nullptr, 0, &process );
71  osl_freeSecurityHandle( security );
72  if( error != osl_Process_E_None )
73  {
74  SAL_WARN( "opencl", "failed to start CL driver test: " << error );
75  return false;
76  }
77  // If the driver takes more than 10 seconds, it's probably broken/useless.
78  TimeValue timeout( 10, 0 );
79  error = osl_joinProcessWithTimeout( process, &timeout );
80  if( error == osl_Process_E_None )
81  {
82  oslProcessInfo info;
83  info.Size = sizeof( info );
84  error = osl_getProcessInfo( process, osl_Process_EXITCODE, &info );
85  if( error == osl_Process_E_None )
86  {
87  if( info.Code == 0 )
88  {
89  SAL_INFO( "opencl", "CL driver test passed" );
90  osl_freeProcessHandle( process );
91  return true;
92  }
93  else
94  {
95  SAL_WARN( "opencl", "CL driver test failed - disabling: " << info.Code );
96  osl_freeProcessHandle( process );
97  return false;
98  }
99  }
100  }
101  SAL_WARN( "opencl", "CL driver test did not finish - disabling: " << error );
102  osl_terminateProcess( process );
103  osl_freeProcessHandle( process );
104  return false;
105 }
106 
107 static bool testOpenCLCompute(const Reference< XDesktop2 > &xDesktop, const OUString &rURL)
108 {
109  bool bSuccess = false;
110  css::uno::Reference< css::lang::XComponent > xComponent;
111 
112  sal_uInt64 nKernelFailures = openclwrapper::kernelFailures;
113 
114  SAL_INFO("opencl", "Starting CL test spreadsheet");
115 
116  // A stale lock file would make the loading fail, so make sure to remove it.
117  try {
118  ::svt::DocumentLockFile lockFile( rURL );
119  lockFile.RemoveFileDirectly();
120  }
121  catch (const css::uno::Exception&)
122  {
123  }
124 
125  try {
126  css::uno::Reference< css::frame::XComponentLoader > xLoader(xDesktop, css::uno::UNO_QUERY_THROW);
127 
128  css::uno::Sequence< css::beans::PropertyValue > aArgs(1);
129  aArgs[0].Name = "Hidden";
130  aArgs[0].Value <<= true;
131 
132  xComponent.set(xLoader->loadComponentFromURL(rURL, "_blank", 0, aArgs));
133 
134  // What an unpleasant API to use.
135  css::uno::Reference< css::sheet::XCalculatable > xCalculatable( xComponent, css::uno::UNO_QUERY_THROW);
136  css::uno::Reference< css::sheet::XSpreadsheetDocument > xSpreadDoc( xComponent, css::uno::UNO_QUERY_THROW );
137  css::uno::Reference< css::sheet::XSpreadsheets > xSheets( xSpreadDoc->getSheets(), css::uno::UNO_SET_THROW );
138  css::uno::Reference< css::container::XIndexAccess > xIndex( xSheets, css::uno::UNO_QUERY_THROW );
139  css::uno::Reference< css::sheet::XSpreadsheet > xSheet( xIndex->getByIndex(0), css::uno::UNO_QUERY_THROW);
140 
141  // So we insert our MAX call at the end on a named range.
142  css::uno::Reference< css::table::XCell2 > xThresh( xSheet->getCellByPosition(1,1), css::uno::UNO_QUERY_THROW ); // B2
143  double fThreshold = xThresh->getValue();
144 
145  // We need pure OCL formulae all the way through the
146  // dependency chain, or we fall-back.
147  xCalculatable->calculateAll();
148 
149  // So we insert our MAX call at the end on a named range.
150  css::uno::Reference< css::table::XCell2 > xCell( xSheet->getCellByPosition(1,0), css::uno::UNO_QUERY_THROW );
151  xCell->setFormula("=MAX(results)");
152  double fResult = xCell->getValue();
153 
154  // Ensure the maximum variance is below our tolerance.
155  if (fResult > fThreshold)
156  {
157  SAL_WARN("opencl", "OpenCL results unstable - disabling; result: "
158  << fResult << " vs. " << fThreshold);
159  }
160  else
161  {
162  SAL_INFO("opencl", "calculating smoothly; result: " << fResult);
163  bSuccess = true;
164  }
165  }
166  catch (const css::uno::Exception &)
167  {
168  TOOLS_WARN_EXCEPTION("opencl", "OpenCL testing failed - disabling");
169  }
170 
171  if (nKernelFailures != openclwrapper::kernelFailures)
172  {
173  // tdf#100883 - defeat SEH exception handling fallbacks.
174  SAL_WARN("opencl", "OpenCL kernels failed to compile, "
175  "or took SEH exceptions "
176  << nKernelFailures << " != " << openclwrapper::kernelFailures);
177  bSuccess = false;
178  }
179 
180  if (!bSuccess)
182  if (xComponent.is())
183  xComponent->dispose();
184 
185 
186  return bSuccess;
187 }
188 
189 void Desktop::CheckOpenCLCompute(const Reference< XDesktop2 > &xDesktop)
190 {
192  return;
193 
194  SAL_INFO("opencl", "Initiating test of OpenCL device");
195  OpenCLZone aZone;
197 
198  OUString aDevice = officecfg::Office::Calc::Formula::Calculation::OpenCLDevice::get();
199  OUString aSelectedCLDeviceVersionID;
201  &aDevice,
202  officecfg::Office::Calc::Formula::Calculation::OpenCLAutoSelect::get(),
203  false /* bForceEvaluation */,
204  aSelectedCLDeviceVersionID))
205  {
206  SAL_WARN("opencl", "Failed to initialize OpenCL for test");
208  return;
209  }
210 
211  // Append our app version as well.
212  aSelectedCLDeviceVersionID += "--" LIBO_VERSION_DOTTED;
213 
214  // Append timestamp of the file.
215  OUString aURL("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/opencl/cl-test.ods");
216  rtl::Bootstrap::expandMacros(aURL);
217 
218  DirectoryItem aItem;
219  (void)DirectoryItem::get( aURL, aItem );
220  FileStatus aFileStatus( osl_FileStatus_Mask_ModifyTime );
221  (void)aItem.getFileStatus( aFileStatus );
222  TimeValue aTimeVal = aFileStatus.getModifyTime();
223  aSelectedCLDeviceVersionID += "--" +
224  OUString::number(aTimeVal.Seconds);
225 
226  if (aSelectedCLDeviceVersionID != officecfg::Office::Common::Misc::SelectedOpenCLDeviceIdentifier::get())
227  {
228  // OpenCL device changed - sanity check it and disable if bad.
229 
230  boost::optional<sal_Int32> nOrigMinimumSize = officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::get();
231  { // set the minimum group size to something small for quick testing.
232  std::shared_ptr<comphelper::ConfigurationChanges> xBatch(comphelper::ConfigurationChanges::create());
233  officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::set(3 /* small */, xBatch);
234  xBatch->commit();
235  }
236 
237  // Hopefully at least basic functionality always works and broken OpenCL implementations break
238  // only when they are used to compute something. If this assumptions turns out to be not true,
239  // the driver check needs to be moved sooner.
240  bool bSucceeded = testOpenCLDriver() && testOpenCLCompute(xDesktop, aURL);
241 
242  { // restore the minimum group size
243  std::shared_ptr<comphelper::ConfigurationChanges> xBatch(comphelper::ConfigurationChanges::create());
244  officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::set(nOrigMinimumSize, xBatch);
245  officecfg::Office::Common::Misc::SelectedOpenCLDeviceIdentifier::set(aSelectedCLDeviceVersionID, xBatch);
246  xBatch->commit();
247  }
248 
249  if (!bSucceeded)
251  }
252 }
253 #endif // HAVE_FEATURE_OPENCL
254 
255 } // end namespace desktop
256 
257 /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
sal_uInt64 kernelFailures
FileStatus
void getOpenCLDeviceName(OUString &rDeviceName, OUString &rPlatformName)
bool switchOpenCLDevice(const OUString *pDeviceId, bool bAutoSelect, bool bForceEvaluation, OUString &rOutSelectedDeviceVersionIDString)
tuple args
Definition: app.cxx:166
static bool IsSafeModeEnabled()
static void hardDisable()
bool canUseOpenCL()
static std::shared_ptr< ConfigurationChanges > create(css::uno::Reference< css::uno::XComponentContext > const &context=comphelper::getProcessComponentContext())
css::uno::Reference< css::lang::XComponent > xComponent
#define TOOLS_WARN_EXCEPTION(area, stream)
static void CheckOpenCLCompute(const css::uno::Reference< css::frame::XDesktop2 > &)
#define SAL_INFO(area, stream)
#define SAL_WARN(area, stream)
static void enterInitialTest()
typedef void(CALLTYPE *GetFuncDataPtr)(sal_uInt16 &nNo