nss: upgrade to release 3.73
[LibreOffice.git] / vcl / skia / SkiaHelper.cxx
blobd545cc670bb54edc31dcc3f5e636deeec953e2a6
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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 */
10 #include <vcl/skia/SkiaHelper.hxx>
12 #if !HAVE_FEATURE_SKIA
14 namespace SkiaHelper
16 bool isVCLSkiaEnabled() { return false; }
18 } // namespace
20 #else
22 #include <rtl/bootstrap.hxx>
23 #include <vcl/svapp.hxx>
24 #include <desktop/crashreport.hxx>
25 #include <officecfg/Office/Common.hxx>
26 #include <watchdog.hxx>
27 #include <skia/zone.hxx>
28 #include <sal/log.hxx>
29 #include <driverblocklist.hxx>
30 #include <skia/utils.hxx>
31 #include <config_folders.h>
32 #include <osl/file.hxx>
33 #include <tools/stream.hxx>
34 #include <list>
35 #include <string_view>
37 #include <SkCanvas.h>
38 #include <SkPaint.h>
39 #include <SkSurface.h>
40 #include <SkGraphics.h>
41 #include <GrDirectContext.h>
42 #include <skia_compiler.hxx>
43 #include <skia_opts.hxx>
45 #ifdef DBG_UTIL
46 #include <fstream>
47 #endif
49 namespace SkiaHelper
51 static OUString getDenylistFile()
53 OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
54 rtl::Bootstrap::expandMacros(url);
56 return url + "/skia/skia_denylist_vulkan.xml";
59 static uint32_t driverVersion = 0;
60 uint32_t vendorId = 0;
62 static OUString versionAsString(uint32_t version)
64 return OUString::number(version >> 22) + "." + OUString::number((version >> 12) & 0x3ff) + "."
65 + OUString::number(version & 0xfff);
68 static std::string_view vendorAsString(uint32_t vendor)
70 return DriverBlocklist::GetVendorNameFromId(vendor);
73 static OUString getCacheFolder()
75 OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
76 "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
77 rtl::Bootstrap::expandMacros(url);
78 osl::Directory::create(url);
79 return url;
82 static void writeToLog(SvStream& stream, const char* key, const char* value)
84 stream.WriteCharPtr(key);
85 stream.WriteCharPtr(": ");
86 stream.WriteCharPtr(value);
87 stream.WriteChar('\n');
90 static void writeToLog(SvStream& stream, const char* key, const OUString& value)
92 writeToLog(stream, key, OUStringToOString(value, RTL_TEXTENCODING_UTF8).getStr());
95 // Note that this function also logs system information about Vulkan.
96 static bool isVulkanDenylisted(const VkPhysicalDeviceProperties& props)
98 static const char* const types[]
99 = { "other", "integrated", "discrete", "virtual", "cpu", "??" }; // VkPhysicalDeviceType
100 driverVersion = props.driverVersion;
101 vendorId = props.vendorID;
102 OUString vendorIdStr = "0x" + OUString::number(props.vendorID, 16);
103 OUString deviceIdStr = "0x" + OUString::number(props.deviceID, 16);
104 OUString driverVersionString = versionAsString(driverVersion);
105 OUString apiVersion = versionAsString(props.apiVersion);
106 const char* deviceType = types[std::min<unsigned>(props.deviceType, SAL_N_ELEMENTS(types) - 1)];
108 CrashReporter::addKeyValue("VulkanVendor", vendorIdStr, CrashReporter::AddItem);
109 CrashReporter::addKeyValue("VulkanDevice", deviceIdStr, CrashReporter::AddItem);
110 CrashReporter::addKeyValue("VulkanAPI", apiVersion, CrashReporter::AddItem);
111 CrashReporter::addKeyValue("VulkanDriver", driverVersionString, CrashReporter::AddItem);
112 CrashReporter::addKeyValue("VulkanDeviceType", OUString::createFromAscii(deviceType),
113 CrashReporter::AddItem);
114 CrashReporter::addKeyValue("VulkanDeviceName", OUString::createFromAscii(props.deviceName),
115 CrashReporter::Write);
117 SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
118 writeToLog(logFile, "RenderMethod", "vulkan");
119 writeToLog(logFile, "Vendor", vendorIdStr);
120 writeToLog(logFile, "Device", deviceIdStr);
121 writeToLog(logFile, "API", apiVersion);
122 writeToLog(logFile, "Driver", driverVersionString);
123 writeToLog(logFile, "DeviceType", deviceType);
124 writeToLog(logFile, "DeviceName", props.deviceName);
126 SAL_INFO("vcl.skia",
127 "Vulkan API version: " << apiVersion << ", driver version: " << driverVersionString
128 << ", vendor: " << vendorIdStr << " ("
129 << vendorAsString(vendorId) << "), device: " << deviceIdStr
130 << ", type: " << deviceType << ", name: " << props.deviceName);
131 bool denylisted
132 = DriverBlocklist::IsDeviceBlocked(getDenylistFile(), DriverBlocklist::VersionType::Vulkan,
133 driverVersionString, vendorIdStr, deviceIdStr);
134 writeToLog(logFile, "Denylisted", denylisted ? "yes" : "no");
135 return denylisted;
138 static void writeSkiaRasterInfo()
140 SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
141 writeToLog(logFile, "RenderMethod", "raster");
142 // Log compiler, Skia works best when compiled with Clang.
143 writeToLog(logFile, "Compiler", skia_compiler_name());
146 static sk_app::VulkanWindowContext::SharedGrDirectContext getTemporaryGrDirectContext();
148 static void checkDeviceDenylisted(bool blockDisable = false)
150 static bool done = false;
151 if (done)
152 return;
154 SkiaZone zone;
156 switch (renderMethodToUse())
158 case RenderVulkan:
160 // First try if a GrDirectContext already exists.
161 sk_app::VulkanWindowContext::SharedGrDirectContext grDirectContext
162 = sk_app::VulkanWindowContext::getSharedGrDirectContext();
163 if (!grDirectContext.getGrDirectContext())
165 // This function is called from isVclSkiaEnabled(), which
166 // may be called when deciding which X11 visual to use,
167 // and that visual is normally needed when creating
168 // Skia's VulkanWindowContext, which is needed for the GrDirectContext.
169 // Avoid the loop by creating a temporary GrDirectContext
170 // that will use the default X11 visual (that shouldn't matter
171 // for just finding out information about Vulkan) and destroying
172 // the temporary context will clean up again.
173 grDirectContext = getTemporaryGrDirectContext();
175 bool denylisted = true; // assume the worst
176 if (grDirectContext.getGrDirectContext()) // Vulkan was initialized properly
178 denylisted
179 = isVulkanDenylisted(sk_app::VulkanWindowContext::getPhysDeviceProperties());
180 SAL_INFO("vcl.skia", "Vulkan denylisted: " << denylisted);
182 else
183 SAL_INFO("vcl.skia", "Vulkan could not be initialized");
184 if (denylisted && !blockDisable)
186 disableRenderMethod(RenderVulkan);
187 writeSkiaRasterInfo();
189 break;
191 case RenderRaster:
192 SAL_INFO("vcl.skia", "Using Skia raster mode");
193 writeSkiaRasterInfo();
194 return; // software, never denylisted
196 done = true;
199 static bool skiaSupportedByBackend = false;
200 static bool supportsVCLSkia()
202 if (!skiaSupportedByBackend)
204 SAL_INFO("vcl.skia", "Skia not supported by VCL backend, disabling");
205 return false;
207 return getenv("SAL_DISABLESKIA") == nullptr;
210 bool isVCLSkiaEnabled()
213 * The !bSet part should only be called once! Changing the results in the same
214 * run will mix Skia and normal rendering.
217 static bool bSet = false;
218 static bool bEnable = false;
219 static bool bForceSkia = false;
221 // No hardware rendering, so no Skia
222 // TODO SKIA
223 if (Application::IsBitmapRendering())
224 return false;
226 if (bSet)
228 return bForceSkia || bEnable;
232 * There are a number of cases that these environment variables cover:
233 * * SAL_FORCESKIA forces Skia if disabled by UI options or denylisted
234 * * SAL_DISABLESKIA avoids the use of Skia regardless of any option
237 bSet = true;
238 bForceSkia = !!getenv("SAL_FORCESKIA") || officecfg::Office::Common::VCL::ForceSkia::get();
240 bool bRet = false;
241 bool bSupportsVCLSkia = supportsVCLSkia();
242 if (bForceSkia && bSupportsVCLSkia)
244 bRet = true;
245 SkGraphics::Init();
246 SkLoOpts::Init();
247 // don't actually block if denylisted, but log it if enabled, and also get the vendor id
248 checkDeviceDenylisted(true);
250 else if (getenv("SAL_FORCEGL"))
252 // Skia usage is checked before GL usage, so if GL is forced (and Skia is not), do not
253 // enable Skia in order to allow GL.
254 bRet = false;
256 else if (bSupportsVCLSkia)
258 static bool bEnableSkiaEnv = !!getenv("SAL_ENABLESKIA");
260 bEnable = bEnableSkiaEnv;
262 if (officecfg::Office::Common::VCL::UseSkia::get())
263 bEnable = true;
265 // Force disable in safe mode
266 if (Application::IsSafeModeEnabled())
267 bEnable = false;
269 if (bEnable)
271 SkGraphics::Init();
272 SkLoOpts::Init();
273 checkDeviceDenylisted(); // switch to raster if driver is denylisted
276 bRet = bEnable;
279 if (bRet)
280 WatchdogThread::start();
282 CrashReporter::addKeyValue("UseSkia", OUString::boolean(bRet), CrashReporter::Write);
284 return bRet;
287 static RenderMethod methodToUse = RenderRaster;
289 static bool initRenderMethodToUse()
291 if (const char* env = getenv("SAL_SKIA"))
293 if (strcmp(env, "raster") == 0)
295 methodToUse = RenderRaster;
296 return true;
298 if (strcmp(env, "vulkan") == 0)
300 methodToUse = RenderVulkan;
301 return true;
303 SAL_WARN("vcl.skia", "Unrecognized value of SAL_SKIA");
304 abort();
306 if (officecfg::Office::Common::VCL::ForceSkiaRaster::get())
308 methodToUse = RenderRaster;
309 return true;
311 methodToUse = RenderVulkan;
312 return true;
315 RenderMethod renderMethodToUse()
317 static bool methodToUseInited = initRenderMethodToUse();
318 if (methodToUseInited) // Used just to ensure thread-safe one-time init.
319 return methodToUse;
320 abort();
323 void disableRenderMethod(RenderMethod method)
325 if (renderMethodToUse() != method)
326 return;
327 // Choose a fallback, right now always raster.
328 methodToUse = RenderRaster;
331 static sk_app::VulkanWindowContext::SharedGrDirectContext* sharedGrDirectContext;
333 static std::unique_ptr<sk_app::WindowContext> (*createVulkanWindowContextFunction)(bool) = nullptr;
334 static void setCreateVulkanWindowContext(std::unique_ptr<sk_app::WindowContext> (*function)(bool))
336 createVulkanWindowContextFunction = function;
339 GrDirectContext* getSharedGrDirectContext()
341 SkiaZone zone;
342 assert(renderMethodToUse() == RenderVulkan);
343 if (sharedGrDirectContext)
344 return sharedGrDirectContext->getGrDirectContext();
345 // TODO mutex?
346 // Set up the shared GrDirectContext from Skia's (patched) VulkanWindowContext, if it's been
347 // already set up.
348 sk_app::VulkanWindowContext::SharedGrDirectContext context
349 = sk_app::VulkanWindowContext::getSharedGrDirectContext();
350 GrDirectContext* grDirectContext = context.getGrDirectContext();
351 if (grDirectContext)
353 sharedGrDirectContext = new sk_app::VulkanWindowContext::SharedGrDirectContext(context);
354 return grDirectContext;
356 static bool done = false;
357 if (done)
358 return nullptr;
359 done = true;
360 if (createVulkanWindowContextFunction == nullptr)
361 return nullptr; // not initialized properly (e.g. used from a VCL backend with no Skia support)
362 std::unique_ptr<sk_app::WindowContext> tmpContext = createVulkanWindowContextFunction(false);
363 // Set up using the shared context created by the call above, if successful.
364 context = sk_app::VulkanWindowContext::getSharedGrDirectContext();
365 grDirectContext = context.getGrDirectContext();
366 if (grDirectContext)
368 sharedGrDirectContext = new sk_app::VulkanWindowContext::SharedGrDirectContext(context);
369 return grDirectContext;
371 disableRenderMethod(RenderVulkan);
372 return nullptr;
375 static sk_app::VulkanWindowContext::SharedGrDirectContext getTemporaryGrDirectContext()
377 if (createVulkanWindowContextFunction == nullptr)
378 return sk_app::VulkanWindowContext::SharedGrDirectContext();
379 std::unique_ptr<sk_app::WindowContext> tmpContext = createVulkanWindowContextFunction(true);
380 // Set up using the shared context created by the call above, if successful.
381 return sk_app::VulkanWindowContext::getSharedGrDirectContext();
384 sk_sp<SkSurface> createSkSurface(int width, int height, SkColorType type, SkAlphaType alpha)
386 SkiaZone zone;
387 assert(type == kN32_SkColorType || type == kAlpha_8_SkColorType);
388 sk_sp<SkSurface> surface;
389 switch (SkiaHelper::renderMethodToUse())
391 case SkiaHelper::RenderVulkan:
393 if (GrDirectContext* grDirectContext = getSharedGrDirectContext())
395 surface = SkSurface::MakeRenderTarget(grDirectContext, SkBudgeted::kNo,
396 SkImageInfo::Make(width, height, type, alpha),
397 0, surfaceProps());
398 if (surface)
400 #ifdef DBG_UTIL
401 prefillSurface(surface);
402 #endif
403 return surface;
405 SAL_WARN("vcl.skia",
406 "cannot create Vulkan GPU offscreen surface, falling back to Raster");
408 break;
410 default:
411 break;
413 // Create raster surface as a fallback.
414 surface = SkSurface::MakeRaster(SkImageInfo::Make(width, height, type, alpha), surfaceProps());
415 assert(surface);
416 if (surface)
418 #ifdef DBG_UTIL
419 prefillSurface(surface);
420 #endif
421 return surface;
423 // In non-debug builds we could return SkSurface::MakeNull() and try to cope with the situation,
424 // but that can lead to unnoticed data loss, so better fail clearly.
425 abort();
428 sk_sp<SkImage> createSkImage(const SkBitmap& bitmap)
430 SkiaZone zone;
431 assert(bitmap.colorType() == kN32_SkColorType || bitmap.colorType() == kAlpha_8_SkColorType);
432 switch (SkiaHelper::renderMethodToUse())
434 case SkiaHelper::RenderVulkan:
436 if (GrDirectContext* grDirectContext = getSharedGrDirectContext())
438 sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(
439 grDirectContext, SkBudgeted::kNo,
440 bitmap.info().makeAlphaType(kPremul_SkAlphaType), 0, surfaceProps());
441 if (surface)
443 SkPaint paint;
444 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
445 surface->getCanvas()->drawBitmap(bitmap, 0, 0, &paint);
446 return makeCheckedImageSnapshot(surface);
448 // Try to fall back in non-debug builds.
449 SAL_WARN("vcl.skia",
450 "cannot create Vulkan GPU offscreen surface, falling back to Raster");
452 break;
454 default:
455 break;
457 // Create raster image as a fallback.
458 sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
459 assert(image);
460 return image;
463 sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface)
465 sk_sp<SkImage> ret = surface->makeImageSnapshot();
466 assert(ret);
467 if (ret)
468 return ret;
469 abort();
472 sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface, const SkIRect& bounds)
474 sk_sp<SkImage> ret = surface->makeImageSnapshot(bounds);
475 assert(ret);
476 if (ret)
477 return ret;
478 abort();
481 namespace
483 // Image cache, for saving results of complex operations such as drawTransformedBitmap().
484 struct ImageCacheItem
486 OString key;
487 sk_sp<SkImage> image;
488 tools::Long size; // cost of the item
490 } //namespace
492 // LRU cache, last item is the least recently used. Hopefully there won't be that many items
493 // to require a hash/map. Using o3tl::lru_cache would be simpler, but it doesn't support
494 // calculating cost of each item.
495 static std::list<ImageCacheItem>* imageCache = nullptr;
496 static tools::Long imageCacheSize = 0; // sum of all ImageCacheItem.size
498 void addCachedImage(const OString& key, sk_sp<SkImage> image)
500 static bool disabled = getenv("SAL_DISABLE_SKIA_CACHE") != nullptr;
501 if (disabled)
502 return;
503 if (imageCache == nullptr)
504 imageCache = new std::list<ImageCacheItem>;
505 tools::Long size = static_cast<tools::Long>(image->width()) * image->height()
506 * SkColorTypeBytesPerPixel(image->imageInfo().colorType());
507 imageCache->push_front({ key, image, size });
508 imageCacheSize += size;
509 SAL_INFO("vcl.skia.trace", "addcachedimage " << image << " :" << size << "/" << imageCacheSize);
510 const tools::Long maxSize = maxImageCacheSize();
511 while (imageCacheSize > maxSize)
513 assert(!imageCache->empty());
514 imageCacheSize -= imageCache->back().size;
515 SAL_INFO("vcl.skia.trace", "least used removal " << imageCache->back().image << ":"
516 << imageCache->back().size);
517 imageCache->pop_back();
521 sk_sp<SkImage> findCachedImage(const OString& key)
523 if (imageCache != nullptr)
525 for (auto it = imageCache->begin(); it != imageCache->end(); ++it)
527 if (it->key == key)
529 sk_sp<SkImage> ret = it->image;
530 SAL_INFO("vcl.skia.trace",
531 "findcachedimage " << key << " : " << it->image << " found");
532 imageCache->splice(imageCache->begin(), *imageCache, it);
533 return ret;
537 SAL_INFO("vcl.skia.trace", "findcachedimage " << key << " not found");
538 return nullptr;
541 void removeCachedImage(sk_sp<SkImage> image)
543 if (imageCache == nullptr)
544 return;
545 for (auto it = imageCache->begin(); it != imageCache->end();)
547 if (it->image == image)
549 imageCacheSize -= it->size;
550 assert(imageCacheSize >= 0);
551 it = imageCache->erase(it);
553 else
554 ++it;
558 tools::Long maxImageCacheSize()
560 // Defaults to 4x 2000px 32bpp images, 64MiB.
561 return officecfg::Office::Common::Cache::Skia::ImageCacheSize::get();
564 void cleanup()
566 delete sharedGrDirectContext;
567 sharedGrDirectContext = nullptr;
568 delete imageCache;
569 imageCache = nullptr;
570 imageCacheSize = 0;
573 static SkSurfaceProps commonSurfaceProps;
574 const SkSurfaceProps* surfaceProps() { return &commonSurfaceProps; }
576 void setPixelGeometry(SkPixelGeometry pixelGeometry)
578 commonSurfaceProps = SkSurfaceProps(commonSurfaceProps.flags(), pixelGeometry);
581 // Skia should not be used from VCL backends that do not actually support it, as there will be setup missing.
582 // The code here (that is in the vcl lib) needs a function for creating Vulkan context that is
583 // usually available only in the backend libs.
584 void prepareSkia(std::unique_ptr<sk_app::WindowContext> (*createVulkanWindowContext)(bool))
586 setCreateVulkanWindowContext(createVulkanWindowContext);
587 skiaSupportedByBackend = true;
590 #ifdef DBG_UTIL
591 void prefillSurface(const sk_sp<SkSurface>& surface)
593 // Pre-fill the surface with deterministic garbage.
594 SkBitmap bitmap;
595 bitmap.allocN32Pixels(2, 2);
596 SkPMColor* scanline;
597 scanline = bitmap.getAddr32(0, 0);
598 *scanline++ = SkPreMultiplyARGB(0xFF, 0xBF, 0x80, 0x40);
599 *scanline++ = SkPreMultiplyARGB(0xFF, 0x40, 0x80, 0xBF);
600 scanline = bitmap.getAddr32(0, 1);
601 *scanline++ = SkPreMultiplyARGB(0xFF, 0xE3, 0x5C, 0x13);
602 *scanline++ = SkPreMultiplyARGB(0xFF, 0x13, 0x5C, 0xE3);
603 bitmap.setImmutable();
604 SkPaint paint;
605 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
606 paint.setShader(bitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat));
607 surface->getCanvas()->drawPaint(paint);
610 void dump(const SkBitmap& bitmap, const char* file) { dump(SkImage::MakeFromBitmap(bitmap), file); }
612 void dump(const sk_sp<SkSurface>& surface, const char* file)
614 surface->getCanvas()->flush();
615 dump(makeCheckedImageSnapshot(surface), file);
618 void dump(const sk_sp<SkImage>& image, const char* file)
620 sk_sp<SkData> data = image->encodeToData();
621 std::ofstream ostream(file, std::ios::binary);
622 ostream.write(static_cast<const char*>(data->data()), data->size());
625 #endif
627 } // namespace
629 #endif // HAVE_FEATURE_SKIA
631 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */