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
30
31#include <com/sun/star/table/XCell2.hpp>
32#include <com/sun/star/sheet/XCalculatable.hpp>
33#include <com/sun/star/sheet/XSpreadsheet.hpp>
34#include <com/sun/star/sheet/XSpreadsheets.hpp>
35#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
36
37#if HAVE_FEATURE_OPENCL
39#endif
40#include <opencl/OpenCLZone.hxx>
41
42#include <osl/file.hxx>
43#include <osl/process.h>
44
45using namespace ::osl;
46using namespace ::com::sun::star::uno;
47using namespace ::com::sun::star::frame;
48
49namespace desktop {
50
51#if HAVE_FEATURE_OPENCL
52
53static bool testOpenCLDriver()
54{
55 // A simple OpenCL test run in a separate process in order to test
56 // whether the driver crashes (asserts,etc.) when trying to use OpenCL.
57 SAL_INFO("opencl", "Starting CL driver test");
58
59 OUString testerURL("$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/opencltest");
60 rtl::Bootstrap::expandMacros(testerURL); //TODO: detect failure
61
62 OUString deviceName, platformName;
63 openclwrapper::getOpenCLDeviceName( deviceName, platformName );
64 rtl_uString* args[] = { deviceName.pData, platformName.pData };
65 sal_Int32 numArgs = 2;
66
67 oslProcess process;
68 oslSecurity security = osl_getCurrentSecurity();
69 oslProcessError error = osl_executeProcess(testerURL.pData, args, numArgs,
70 osl_Process_SEARCHPATH | osl_Process_HIDDEN, security,
71 nullptr, nullptr, 0, &process );
72 osl_freeSecurityHandle( security );
73 if( error != osl_Process_E_None )
74 {
75 SAL_WARN( "opencl", "failed to start CL driver test: " << error );
76 return false;
77 }
78 // If the driver takes more than 10 seconds, it's probably broken/useless.
79 TimeValue timeout( 10, 0 );
80 error = osl_joinProcessWithTimeout( process, &timeout );
81 if( error == osl_Process_E_None )
82 {
83 oslProcessInfo info;
84 info.Size = sizeof( info );
85 error = osl_getProcessInfo( process, osl_Process_EXITCODE, &info );
86 if( error == osl_Process_E_None )
87 {
88 if( info.Code == 0 )
89 {
90 SAL_INFO( "opencl", "CL driver test passed" );
91 osl_freeProcessHandle( process );
92 return true;
93 }
94 else
95 {
96 SAL_WARN( "opencl", "CL driver test failed - disabling: " << info.Code );
97 osl_freeProcessHandle( process );
98 return false;
99 }
100 }
101 }
102 SAL_WARN( "opencl", "CL driver test did not finish - disabling: " << error );
103 osl_terminateProcess( process );
104 osl_freeProcessHandle( process );
105 return false;
106}
107
108static bool testOpenCLCompute(const Reference< XDesktop2 > &xDesktop, const OUString &rURL)
109{
110 bool bSuccess = false;
111 css::uno::Reference< css::lang::XComponent > xComponent;
112
113 sal_uInt64 nKernelFailures = openclwrapper::kernelFailures;
114
115 SAL_INFO("opencl", "Starting CL test spreadsheet");
116
117 // A stale lock file would make the loading fail, so make sure to remove it.
118 try {
119 ::svt::DocumentLockFile lockFile( rURL );
120 lockFile.RemoveFileDirectly();
121 }
122 catch (const css::uno::Exception&)
123 {
124 }
125
126 try {
127 css::uno::Reference< css::frame::XComponentLoader > xLoader(xDesktop, css::uno::UNO_QUERY_THROW);
128
129 css::uno::Sequence< css::beans::PropertyValue > aArgs{ comphelper::makePropertyValue("Hidden",
130 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
190{
192 return;
193
194 SAL_INFO("opencl", "Initiating test of OpenCL device");
195 OpenCLZone aZone;
196 OpenCLInitialZone aInitialZone;
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 return;
228
229 // OpenCL device changed - sanity check it and disable if bad.
230
231 sal_Int32 nOrigMinimumSize = officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::get();
232 { // set the minimum group size to something small for quick testing.
233 std::shared_ptr<comphelper::ConfigurationChanges> xBatch(comphelper::ConfigurationChanges::create());
234 officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::set(3 /* small */, xBatch);
235 xBatch->commit();
236 }
237
238 // Hopefully at least basic functionality always works and broken OpenCL implementations break
239 // only when they are used to compute something. If this assumptions turns out to be not true,
240 // the driver check needs to be moved sooner.
241 bool bSucceeded = testOpenCLDriver() && testOpenCLCompute(xDesktop, aURL);
242
243 { // restore the minimum group size
244 std::shared_ptr<comphelper::ConfigurationChanges> xBatch(comphelper::ConfigurationChanges::create());
245 officecfg::Office::Calc::Formula::Calculation::OpenCLMinimumDataSize::set(nOrigMinimumSize, xBatch);
246 officecfg::Office::Common::Misc::SelectedOpenCLDeviceIdentifier::set(aSelectedCLDeviceVersionID, xBatch);
247 xBatch->commit();
248 }
249
250 if (!bSucceeded)
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: */
static bool IsSafeModeEnabled()
static void hardDisable()
static std::shared_ptr< ConfigurationChanges > create()
static void CheckOpenCLCompute(const css::uno::Reference< css::frame::XDesktop2 > &)
#define TOOLS_WARN_EXCEPTION(area, stream)
URL aURL
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
css::beans::PropertyValue makePropertyValue(const OUString &rName, T &&rValue)
Definition: app.cxx:165
FileStatus
bool switchOpenCLDevice(std::u16string_view aDevice, bool bAutoSelect, bool bForceEvaluation, OUString &rOutSelectedDeviceVersionIDString)
void getOpenCLDeviceName(OUString &rDeviceName, OUString &rPlatformName)
sal_uInt64 kernelFailures
bool canUseOpenCL()
args