Update git submodules
[LibreOffice.git] / vcl / skia / SkiaHelper.cxx
blobbd9c5a2c21b2959949b5b1a4564fe53f93dd54f1
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 <sal/config.h>
12 #include <config_features.h>
14 #include <string_view>
16 #include <vcl/skia/SkiaHelper.hxx>
18 #if !HAVE_FEATURE_SKIA
20 namespace SkiaHelper
22 bool isVCLSkiaEnabled() { return false; }
23 bool isAlphaMaskBlendingEnabled() { return false; }
25 } // namespace
27 #else
29 #include <rtl/bootstrap.hxx>
30 #include <vcl/svapp.hxx>
31 #include <desktop/crashreport.hxx>
32 #include <officecfg/Office/Common.hxx>
33 #include <watchdog.hxx>
34 #include <skia/zone.hxx>
35 #include <sal/log.hxx>
36 #include <driverblocklist.hxx>
37 #include <skia/utils.hxx>
38 #include <config_folders.h>
39 #include <config_skia.h>
40 #include <osl/file.hxx>
41 #include <tools/stream.hxx>
42 #include <list>
43 #include <o3tl/lru_map.hxx>
45 #include <SkBitmap.h>
46 #include <SkCanvas.h>
47 #include <include/codec/SkEncodedImageFormat.h>
48 #include <SkPaint.h>
49 #include <SkSurface.h>
50 #include <SkGraphics.h>
51 #include <GrDirectContext.h>
52 #include <SkRuntimeEffect.h>
53 #include <SkStream.h>
54 #include <SkTileMode.h>
55 #include <skia_compiler.hxx>
56 #include <skia_opts.hxx>
57 #if defined(MACOSX)
58 #include <premac.h>
59 #endif
60 #ifdef SK_VULKAN
61 #include <tools/window/VulkanWindowContext.h>
62 #endif
63 #ifdef SK_METAL
64 #include <tools/window/MetalWindowContext.h>
65 #endif
66 #if defined(MACOSX)
67 #include <postmac.h>
68 #endif
69 #include <src/core/SkOpts.h>
70 #include <src/core/SkChecksum.h>
71 #include <include/encode/SkPngEncoder.h>
72 #include <ganesh/SkSurfaceGanesh.h>
73 #if defined _MSC_VER
74 #pragma warning(disable : 4100) // "unreferenced formal parameter"
75 #pragma warning(disable : 4324) // "structure was padded due to alignment specifier"
76 #endif
77 #if defined __clang__
78 #pragma clang diagnostic push
79 #pragma clang diagnostic ignored "-Wunused-parameter"
80 #endif
81 #if defined __GNUC__ && !defined __clang__
82 #pragma GCC diagnostic push
83 #pragma GCC diagnostic ignored "-Wunused-parameter"
84 #endif
85 #include <src/image/SkImage_Base.h>
86 #if defined __GNUC__ && !defined __clang__
87 #pragma GCC diagnostic pop
88 #endif
89 #if defined __clang__
90 #pragma clang diagnostic pop
91 #endif
93 #include <fstream>
95 #ifdef SK_METAL
96 #ifdef MACOSX
97 #include <quartz/cgutils.h>
98 #endif
99 #endif
101 namespace SkiaHelper
103 static OUString getCacheFolder()
105 OUString url(u"${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
106 "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/"_ustr);
107 rtl::Bootstrap::expandMacros(url);
108 osl::Directory::create(url);
109 return url;
112 static void writeToLog(SvStream& stream, const char* key, const char* value)
114 stream.WriteOString(key);
115 stream.WriteOString(": ");
116 stream.WriteOString(value);
117 stream.WriteChar('\n');
120 OUString readLog()
122 SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::READ);
124 OUString sResult;
125 OString sLine;
126 while (logFile.ReadLine(sLine))
127 sResult += OStringToOUString(sLine, RTL_TEXTENCODING_UTF8) + "\n";
129 return sResult;
132 uint32_t vendorId = 0;
134 #ifdef SK_VULKAN
135 static void writeToLog(SvStream& stream, const char* key, std::u16string_view value)
137 writeToLog(stream, key, OUStringToOString(value, RTL_TEXTENCODING_UTF8).getStr());
140 static OUString getDenylistFile()
142 OUString url(u"$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER ""_ustr);
143 rtl::Bootstrap::expandMacros(url);
145 return url + "/skia/skia_denylist_vulkan.xml";
148 static uint32_t driverVersion = 0;
150 static OUString versionAsString(uint32_t version)
152 return OUString::number(version >> 22) + "." + OUString::number((version >> 12) & 0x3ff) + "."
153 + OUString::number(version & 0xfff);
156 static std::string_view vendorAsString(uint32_t vendor)
158 return DriverBlocklist::GetVendorNameFromId(vendor);
161 // Note that this function also logs system information about Vulkan.
162 static bool isVulkanDenylisted(const VkPhysicalDeviceProperties& props)
164 static const char* const types[]
165 = { "other", "integrated", "discrete", "virtual", "cpu", "??" }; // VkPhysicalDeviceType
166 driverVersion = props.driverVersion;
167 vendorId = props.vendorID;
168 OUString vendorIdStr = "0x" + OUString::number(props.vendorID, 16);
169 OUString deviceIdStr = "0x" + OUString::number(props.deviceID, 16);
170 OUString driverVersionString = versionAsString(driverVersion);
171 OUString apiVersion = versionAsString(props.apiVersion);
172 const char* deviceType = types[std::min<unsigned>(props.deviceType, SAL_N_ELEMENTS(types) - 1)];
174 CrashReporter::addKeyValue(u"VulkanVendor"_ustr, vendorIdStr, CrashReporter::AddItem);
175 CrashReporter::addKeyValue(u"VulkanDevice"_ustr, deviceIdStr, CrashReporter::AddItem);
176 CrashReporter::addKeyValue(u"VulkanAPI"_ustr, apiVersion, CrashReporter::AddItem);
177 CrashReporter::addKeyValue(u"VulkanDriver"_ustr, driverVersionString, CrashReporter::AddItem);
178 CrashReporter::addKeyValue(u"VulkanDeviceType"_ustr, OUString::createFromAscii(deviceType),
179 CrashReporter::AddItem);
180 CrashReporter::addKeyValue(u"VulkanDeviceName"_ustr,
181 OUString::createFromAscii(props.deviceName), CrashReporter::Write);
183 SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
184 writeToLog(logFile, "RenderMethod", "vulkan");
185 writeToLog(logFile, "Vendor", vendorIdStr);
186 writeToLog(logFile, "Device", deviceIdStr);
187 writeToLog(logFile, "API", apiVersion);
188 writeToLog(logFile, "Driver", driverVersionString);
189 writeToLog(logFile, "DeviceType", deviceType);
190 writeToLog(logFile, "DeviceName", props.deviceName);
192 SAL_INFO("vcl.skia",
193 "Vulkan API version: " << apiVersion << ", driver version: " << driverVersionString
194 << ", vendor: " << vendorIdStr << " ("
195 << vendorAsString(vendorId) << "), device: " << deviceIdStr
196 << ", type: " << deviceType << ", name: " << props.deviceName);
197 bool denylisted
198 = DriverBlocklist::IsDeviceBlocked(getDenylistFile(), DriverBlocklist::VersionType::Vulkan,
199 driverVersionString, vendorIdStr, deviceIdStr);
200 writeToLog(logFile, "Denylisted", denylisted ? "yes" : "no");
201 return denylisted;
203 #endif
205 #ifdef SK_METAL
206 static void writeSkiaMetalInfo()
208 SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
209 writeToLog(logFile, "RenderMethod", "metal");
211 #endif
213 static void writeSkiaRasterInfo()
215 SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
216 writeToLog(logFile, "RenderMethod", "raster");
217 // Log compiler, Skia works best when compiled with Clang.
218 writeToLog(logFile, "Compiler", skia_compiler_name());
221 #if defined(SK_VULKAN) || defined(SK_METAL)
222 static std::unique_ptr<skwindow::WindowContext> getTemporaryWindowContext();
223 #endif
225 static void checkDeviceDenylisted(bool blockDisable = false)
227 static bool done = false;
228 if (done)
229 return;
231 SkiaZone zone;
233 bool useRaster = false;
234 switch (renderMethodToUse())
236 case RenderVulkan:
238 #ifdef SK_VULKAN
239 // First try if a GrDirectContext already exists.
240 std::unique_ptr<skwindow::WindowContext> temporaryWindowContext;
241 GrDirectContext* grDirectContext
242 = skwindow::internal::VulkanWindowContext::getSharedGrDirectContext();
243 if (!grDirectContext)
245 // This function is called from isVclSkiaEnabled(), which
246 // may be called when deciding which X11 visual to use,
247 // and that visual is normally needed when creating
248 // Skia's VulkanWindowContext, which is needed for the GrDirectContext.
249 // Avoid the loop by creating a temporary WindowContext
250 // that will use the default X11 visual (that shouldn't matter
251 // for just finding out information about Vulkan) and destroying
252 // the temporary context will clean up again.
253 temporaryWindowContext = getTemporaryWindowContext();
254 grDirectContext
255 = skwindow::internal::VulkanWindowContext::getSharedGrDirectContext();
257 bool denylisted = true; // assume the worst
258 if (grDirectContext) // Vulkan was initialized properly
260 denylisted = isVulkanDenylisted(
261 skwindow::internal::VulkanWindowContext::getPhysDeviceProperties());
262 SAL_INFO("vcl.skia", "Vulkan denylisted: " << denylisted);
264 else
265 SAL_INFO("vcl.skia", "Vulkan could not be initialized");
266 if (denylisted && !blockDisable)
268 disableRenderMethod(RenderVulkan);
269 useRaster = true;
271 #else
272 SAL_WARN("vcl.skia", "Vulkan support not built in");
273 (void)blockDisable;
274 useRaster = true;
275 #endif
276 break;
278 case RenderMetal:
280 #ifdef SK_METAL
281 // First try if a GrDirectContext already exists.
282 std::unique_ptr<skwindow::WindowContext> temporaryWindowContext;
283 GrDirectContext* grDirectContext = skwindow::internal::getMetalSharedGrDirectContext();
284 if (!grDirectContext)
286 // Create a temporary window context just to get the GrDirectContext,
287 // as an initial test of Metal functionality.
288 temporaryWindowContext = getTemporaryWindowContext();
289 grDirectContext = skwindow::internal::getMetalSharedGrDirectContext();
291 if (grDirectContext) // Metal was initialized properly
293 #ifdef MACOSX
294 if (!blockDisable && !DefaultMTLDeviceIsSupported())
296 SAL_INFO("vcl.skia", "Metal default device not supported");
297 disableRenderMethod(RenderMetal);
298 useRaster = true;
300 else
301 #endif
303 SAL_INFO("vcl.skia", "Using Skia Metal mode");
304 writeSkiaMetalInfo();
307 else
309 SAL_INFO("vcl.skia", "Metal could not be initialized");
310 disableRenderMethod(RenderMetal);
311 useRaster = true;
313 #else
314 SAL_WARN("vcl.skia", "Metal support not built in");
315 useRaster = true;
316 #endif
317 break;
319 case RenderRaster:
320 useRaster = true;
321 break;
323 if (useRaster)
325 SAL_INFO("vcl.skia", "Using Skia raster mode");
326 // software, never denylisted
327 writeSkiaRasterInfo();
329 done = true;
332 static bool skiaSupportedByBackend = false;
333 static bool supportsVCLSkia()
335 if (!skiaSupportedByBackend)
337 SAL_INFO("vcl.skia", "Skia not supported by VCL backend, disabling");
338 return false;
340 return getenv("SAL_DISABLESKIA") == nullptr;
343 static void initInternal();
345 bool isVCLSkiaEnabled()
348 * The !bSet part should only be called once! Changing the results in the same
349 * run will mix Skia and normal rendering.
352 static bool bSet = false;
353 static bool bEnable = false;
354 static bool bForceSkia = false;
356 // allow global disable when testing SystemPrimitiveRenderer since current Skia on Win does not
357 // harmonize with using Direct2D and D2DPixelProcessor2D
358 static const bool bTestSystemPrimitiveRenderer(
359 nullptr != std::getenv("TEST_SYSTEM_PRIMITIVE_RENDERER"));
360 if (bTestSystemPrimitiveRenderer)
361 return false;
363 if (bSet)
365 return bForceSkia || bEnable;
369 * There are a number of cases that these environment variables cover:
370 * * SAL_FORCESKIA forces Skia if disabled by UI options or denylisted
371 * * SAL_DISABLESKIA avoids the use of Skia regardless of any option
374 bSet = true;
375 bForceSkia = !!getenv("SAL_FORCESKIA") || officecfg::Office::Common::VCL::ForceSkia::get();
377 bool bRet = false;
378 bool bSupportsVCLSkia = supportsVCLSkia();
379 if (bForceSkia && bSupportsVCLSkia)
381 bRet = true;
382 initInternal();
383 // don't actually block if denylisted, but log it if enabled, and also get the vendor id
384 checkDeviceDenylisted(true);
386 else if (getenv("SAL_FORCEGL"))
388 // Skia usage is checked before GL usage, so if GL is forced (and Skia is not), do not
389 // enable Skia in order to allow GL.
390 bRet = false;
392 else if (bSupportsVCLSkia)
394 static bool bEnableSkiaEnv = !!getenv("SAL_ENABLESKIA");
396 bEnable = bEnableSkiaEnv;
398 if (officecfg::Office::Common::VCL::UseSkia::get())
399 bEnable = true;
401 // Force disable in safe mode
402 if (Application::IsSafeModeEnabled())
403 bEnable = false;
405 if (bEnable)
407 initInternal();
408 checkDeviceDenylisted(); // switch to raster if driver is denylisted
411 bRet = bEnable;
414 if (bRet)
415 WatchdogThread::start();
417 CrashReporter::addKeyValue(u"UseSkia"_ustr, OUString::boolean(bRet), CrashReporter::Write);
419 return bRet;
422 bool isAlphaMaskBlendingEnabled() { return false; }
424 static RenderMethod methodToUse = RenderRaster;
426 static bool initRenderMethodToUse()
428 if (Application::IsBitmapRendering())
430 methodToUse = RenderRaster;
431 return true;
434 if (const char* env = getenv("SAL_SKIA"))
436 if (strcmp(env, "raster") == 0)
438 methodToUse = RenderRaster;
439 return true;
441 #ifdef MACOSX
442 if (strcmp(env, "metal") == 0)
444 methodToUse = RenderMetal;
445 return true;
447 #else
448 if (strcmp(env, "vulkan") == 0)
450 methodToUse = RenderVulkan;
451 return true;
453 #endif
454 SAL_WARN("vcl.skia", "Unrecognized value of SAL_SKIA");
455 abort();
457 methodToUse = RenderRaster;
458 if (officecfg::Office::Common::VCL::ForceSkiaRaster::get())
459 return true;
460 #ifdef SK_METAL
461 methodToUse = RenderMetal;
462 #endif
463 #ifdef SK_VULKAN
464 methodToUse = RenderVulkan;
465 #endif
466 return true;
469 RenderMethod renderMethodToUse()
471 static bool methodToUseInited = initRenderMethodToUse();
472 if (methodToUseInited) // Used just to ensure thread-safe one-time init.
473 return methodToUse;
474 abort();
477 void disableRenderMethod(RenderMethod method)
479 if (renderMethodToUse() != method)
480 return;
481 // Choose a fallback, right now always raster.
482 methodToUse = RenderRaster;
485 // If needed, we'll allocate one extra window context so that we have a valid GrDirectContext
486 // from Vulkan/MetalWindowContext.
487 static std::unique_ptr<skwindow::WindowContext> sharedWindowContext;
489 static std::unique_ptr<skwindow::WindowContext> (*createGpuWindowContextFunction)(bool) = nullptr;
490 static void setCreateGpuWindowContext(std::unique_ptr<skwindow::WindowContext> (*function)(bool))
492 createGpuWindowContextFunction = function;
495 GrDirectContext* getSharedGrDirectContext()
497 SkiaZone zone;
498 assert(renderMethodToUse() != RenderRaster);
499 if (sharedWindowContext)
500 return sharedWindowContext->directContext();
501 // TODO mutex?
502 // Set up the shared GrDirectContext from Skia's (patched) Vulkan/MetalWindowContext, if it's been
503 // already set up.
504 switch (renderMethodToUse())
506 case RenderVulkan:
507 #ifdef SK_VULKAN
508 if (GrDirectContext* context
509 = skwindow::internal::VulkanWindowContext::getSharedGrDirectContext())
510 return context;
511 #endif
512 break;
513 case RenderMetal:
514 #ifdef SK_METAL
515 if (GrDirectContext* context = skwindow::internal::getMetalSharedGrDirectContext())
516 return context;
517 #endif
518 break;
519 case RenderRaster:
520 abort();
522 static bool done = false;
523 if (done)
524 return nullptr;
525 done = true;
526 if (createGpuWindowContextFunction == nullptr)
527 return nullptr; // not initialized properly (e.g. used from a VCL backend with no Skia support)
528 sharedWindowContext = createGpuWindowContextFunction(false);
529 GrDirectContext* grDirectContext
530 = sharedWindowContext ? sharedWindowContext->directContext() : nullptr;
531 if (grDirectContext)
532 return grDirectContext;
533 SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia",
534 "Cannot create Vulkan GPU context, falling back to Raster");
535 SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia",
536 "Cannot create Metal GPU context, falling back to Raster");
537 disableRenderMethod(renderMethodToUse());
538 return nullptr;
541 #if defined(SK_VULKAN) || defined(SK_METAL)
542 static std::unique_ptr<skwindow::WindowContext> getTemporaryWindowContext()
544 if (createGpuWindowContextFunction == nullptr)
545 return nullptr;
546 return createGpuWindowContextFunction(true);
548 #endif
550 static RenderMethod renderMethodToUseForSize(const SkISize& size)
552 // Do not use GPU for small surfaces. The problem is that due to the separate alpha hack
553 // we quite often may call GetBitmap() on VirtualDevice, which is relatively slow
554 // when the pixels need to be fetched from the GPU. And there are documents that use
555 // many tiny surfaces (bsc#1183308 for example), where this slowness adds up too much.
556 // This should be re-evaluated once the separate alpha hack is removed (SKIA_USE_BITMAP32)
557 // and we no longer (hopefully) fetch pixels that often.
558 if (size.width() <= 32 && size.height() <= 32)
559 return RenderRaster;
560 return renderMethodToUse();
563 sk_sp<SkSurface> createSkSurface(int width, int height, SkColorType type, SkAlphaType alpha)
565 SkiaZone zone;
566 assert(type == kN32_SkColorType || type == kAlpha_8_SkColorType);
567 sk_sp<SkSurface> surface;
568 switch (renderMethodToUseForSize({ width, height }))
570 case RenderVulkan:
571 case RenderMetal:
573 if (GrDirectContext* grDirectContext = getSharedGrDirectContext())
575 surface = SkSurfaces::RenderTarget(grDirectContext, skgpu::Budgeted::kNo,
576 SkImageInfo::Make(width, height, type, alpha), 0,
577 surfaceProps());
578 if (surface)
580 #ifdef DBG_UTIL
581 prefillSurface(surface);
582 #endif
583 return surface;
585 SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia",
586 "Cannot create Vulkan GPU offscreen surface, falling back to Raster");
587 SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia",
588 "Cannot create Metal GPU offscreen surface, falling back to Raster");
590 break;
592 default:
593 break;
595 // Create raster surface as a fallback.
596 surface = SkSurfaces::Raster(SkImageInfo::Make(width, height, type, alpha), surfaceProps());
597 assert(surface);
598 if (surface)
600 #ifdef DBG_UTIL
601 prefillSurface(surface);
602 #endif
603 return surface;
605 // In non-debug builds we could return SkSurface::MakeNull() and try to cope with the situation,
606 // but that can lead to unnoticed data loss, so better fail clearly.
607 abort();
610 sk_sp<SkImage> createSkImage(const SkBitmap& bitmap)
612 SkiaZone zone;
613 assert(bitmap.colorType() == kN32_SkColorType || bitmap.colorType() == kAlpha_8_SkColorType);
614 switch (renderMethodToUseForSize(bitmap.dimensions()))
616 case RenderVulkan:
617 case RenderMetal:
619 if (GrDirectContext* grDirectContext = getSharedGrDirectContext())
621 sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(
622 grDirectContext, skgpu::Budgeted::kNo,
623 bitmap.info().makeAlphaType(kPremul_SkAlphaType), 0, surfaceProps());
624 if (surface)
626 SkPaint paint;
627 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
628 surface->getCanvas()->drawImage(bitmap.asImage(), 0, 0, SkSamplingOptions(),
629 &paint);
630 return makeCheckedImageSnapshot(surface);
632 // Try to fall back in non-debug builds.
633 SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia",
634 "Cannot create Vulkan GPU offscreen surface, falling back to Raster");
635 SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia",
636 "Cannot create Metal GPU offscreen surface, falling back to Raster");
638 break;
640 default:
641 break;
643 // Create raster image as a fallback.
644 sk_sp<SkImage> image = SkImages::RasterFromBitmap(bitmap);
645 assert(image);
646 return image;
649 sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface)
651 sk_sp<SkImage> ret = surface->makeImageSnapshot();
652 assert(ret);
653 if (ret)
654 return ret;
655 abort();
658 sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface, const SkIRect& bounds)
660 sk_sp<SkImage> ret = surface->makeImageSnapshot(bounds);
661 assert(ret);
662 if (ret)
663 return ret;
664 abort();
667 namespace
669 // Image cache, for saving results of complex operations such as drawTransformedBitmap().
670 struct ImageCacheItem
672 OString key;
673 sk_sp<SkImage> image;
674 tools::Long size; // cost of the item
676 } //namespace
678 // LRU cache, last item is the least recently used. Hopefully there won't be that many items
679 // to require a hash/map. Using o3tl::lru_map would be simpler, but it doesn't support
680 // calculating cost of each item.
681 static std::list<ImageCacheItem> imageCache;
682 static tools::Long imageCacheSize = 0; // sum of all ImageCacheItem.size
684 void addCachedImage(const OString& key, sk_sp<SkImage> image)
686 static bool disabled = getenv("SAL_DISABLE_SKIA_CACHE") != nullptr;
687 if (disabled)
688 return;
689 tools::Long size = static_cast<tools::Long>(image->width()) * image->height()
690 * SkColorTypeBytesPerPixel(image->imageInfo().colorType());
691 imageCache.push_front({ key, image, size });
692 imageCacheSize += size;
693 SAL_INFO("vcl.skia.trace", "addcachedimage " << image << " :" << size << "/" << imageCacheSize);
694 const tools::Long maxSize = maxImageCacheSize();
695 while (imageCacheSize > maxSize)
697 assert(!imageCache.empty());
698 imageCacheSize -= imageCache.back().size;
699 SAL_INFO("vcl.skia.trace",
700 "least used removal " << imageCache.back().image << ":" << imageCache.back().size);
701 imageCache.pop_back();
705 sk_sp<SkImage> findCachedImage(const OString& key)
707 for (auto it = imageCache.begin(); it != imageCache.end(); ++it)
709 if (it->key == key)
711 sk_sp<SkImage> ret = it->image;
712 SAL_INFO("vcl.skia.trace", "findcachedimage " << key << " : " << it->image << " found");
713 imageCache.splice(imageCache.begin(), imageCache, it);
714 return ret;
717 SAL_INFO("vcl.skia.trace", "findcachedimage " << key << " not found");
718 return nullptr;
721 void removeCachedImage(sk_sp<SkImage> image)
723 for (auto it = imageCache.begin(); it != imageCache.end();)
725 if (it->image == image)
727 imageCacheSize -= it->size;
728 assert(imageCacheSize >= 0);
729 it = imageCache.erase(it);
731 else
732 ++it;
736 tools::Long maxImageCacheSize()
738 // Defaults to 4x 2000px 32bpp images, 64MiB.
739 return officecfg::Office::Common::Cache::Skia::ImageCacheSize::get();
742 static o3tl::lru_map<uint32_t, uint32_t> checksumCache(256);
744 static uint32_t computeSkPixmapChecksum(const SkPixmap& pixmap)
746 // Use uint32_t because that's what SkChecksum::Hash32() returns.
747 static_assert(std::is_same_v<uint32_t, decltype(SkChecksum::Hash32(nullptr, 0, 0))>);
748 const size_t dataRowBytes = pixmap.width() << pixmap.shiftPerPixel();
749 if (dataRowBytes == pixmap.rowBytes())
750 return SkChecksum::Hash32(pixmap.addr(), pixmap.height() * dataRowBytes, 0);
751 uint32_t sum = 0;
752 for (int row = 0; row < pixmap.height(); ++row)
753 sum = SkChecksum::Hash32(pixmap.addr(0, row), dataRowBytes, sum);
754 return sum;
757 uint32_t getSkImageChecksum(sk_sp<SkImage> image)
759 // Cache the checksums based on the uniqueID() (which should stay the same
760 // for the same image), because it may be still somewhat expensive.
761 uint32_t id = image->uniqueID();
762 auto it = checksumCache.find(id);
763 if (it != checksumCache.end())
764 return it->second;
765 SkPixmap pixmap;
766 if (!image->peekPixels(&pixmap))
767 abort(); // Fetching of GPU-based pixels is expensive, and shouldn't(?) be needed anyway.
768 uint32_t checksum = computeSkPixmapChecksum(pixmap);
769 checksumCache.insert({ id, checksum });
770 return checksum;
773 static sk_sp<SkBlender> invertBlender;
774 static sk_sp<SkBlender> xorBlender;
776 // This does the invert operation, i.e. result = color(255-R,255-G,255-B,A).
777 void setBlenderInvert(SkPaint* paint)
779 if (!invertBlender)
781 // Note that the colors are premultiplied, so '1 - dst.r' must be
782 // written as 'dst.a - dst.r', since premultiplied R is in the range (0-A).
783 const char* const diff = R"(
784 vec4 main( vec4 src, vec4 dst )
786 return vec4( dst.a - dst.r, dst.a - dst.g, dst.a - dst.b, dst.a );
789 auto effect = SkRuntimeEffect::MakeForBlender(SkString(diff));
790 if (!effect.effect)
792 SAL_WARN("vcl.skia",
793 "SKRuntimeEffect::MakeForBlender failed: " << effect.errorText.c_str());
794 abort();
796 invertBlender = effect.effect->makeBlender(nullptr);
798 paint->setBlender(invertBlender);
801 // This does the xor operation, i.e. bitwise xor of RGB values of both colors.
802 void setBlenderXor(SkPaint* paint)
804 if (!xorBlender)
806 // Note that the colors are premultiplied, converting to 0-255 range
807 // must also unpremultiply.
808 const char* const diff = R"(
809 vec4 main( vec4 src, vec4 dst )
811 return vec4(
812 float(int(src.r * src.a * 255.0) ^ int(dst.r * dst.a * 255.0)) / 255.0 / dst.a,
813 float(int(src.g * src.a * 255.0) ^ int(dst.g * dst.a * 255.0)) / 255.0 / dst.a,
814 float(int(src.b * src.a * 255.0) ^ int(dst.b * dst.a * 255.0)) / 255.0 / dst.a,
815 dst.a );
818 SkRuntimeEffect::Options opts;
819 // Skia does not allow binary operators in the default ES2Strict mode, but that's only
820 // because of OpenGL support. We don't use OpenGL, and it's safe for all modes that we do use.
821 // https://groups.google.com/g/skia-discuss/c/EPLuQbg64Kc/m/2uDXFIGhAwAJ
822 opts.maxVersionAllowed = SkSL::Version::k300;
823 auto effect = SkRuntimeEffect::MakeForBlender(SkString(diff), opts);
824 if (!effect.effect)
826 SAL_WARN("vcl.skia",
827 "SKRuntimeEffect::MakeForBlender failed: " << effect.errorText.c_str());
828 abort();
830 xorBlender = effect.effect->makeBlender(nullptr);
832 paint->setBlender(xorBlender);
835 static void initInternal()
837 // Set up all things needed for using Skia.
838 SkGraphics::Init();
839 SkLoOpts::Init();
842 void cleanup()
844 sharedWindowContext.reset();
845 imageCache.clear();
846 imageCacheSize = 0;
847 invertBlender.reset();
848 xorBlender.reset();
851 static SkSurfaceProps commonSurfaceProps;
852 const SkSurfaceProps* surfaceProps() { return &commonSurfaceProps; }
854 void setPixelGeometry(SkPixelGeometry pixelGeometry)
856 commonSurfaceProps = SkSurfaceProps(commonSurfaceProps.flags(), pixelGeometry);
859 // Skia should not be used from VCL backends that do not actually support it, as there will be setup missing.
860 // The code here (that is in the vcl lib) needs a function for creating Vulkan/Metal context that is
861 // usually available only in the backend libs.
862 void prepareSkia(std::unique_ptr<skwindow::WindowContext> (*createGpuWindowContext)(bool))
864 setCreateGpuWindowContext(createGpuWindowContext);
865 skiaSupportedByBackend = true;
868 void dump(const SkBitmap& bitmap, const char* file)
870 dump(SkImages::RasterFromBitmap(bitmap), file);
873 void dump(const sk_sp<SkSurface>& surface, const char* file)
875 if (auto dContext = GrAsDirectContext(surface->getCanvas()->recordingContext()))
876 dContext->flushAndSubmit();
877 dump(makeCheckedImageSnapshot(surface), file);
880 void dump(const sk_sp<SkImage>& image, const char* file)
882 SkBitmap bm;
883 if (!as_IB(image)->getROPixels(getSharedGrDirectContext(), &bm))
884 return;
885 SkPixmap pixmap;
886 if (!bm.peekPixels(&pixmap))
887 return;
888 SkPngEncoder::Options opts;
889 opts.fFilterFlags = SkPngEncoder::FilterFlag::kNone;
890 opts.fZLibLevel = 1;
891 SkDynamicMemoryWStream stream;
892 if (!SkPngEncoder::Encode(&stream, pixmap, opts))
893 return;
894 sk_sp<SkData> data = stream.detachAsData();
895 std::ofstream ostream(file, std::ios::binary);
896 ostream.write(static_cast<const char*>(data->data()), data->size());
899 #ifdef DBG_UTIL
900 void prefillSurface(const sk_sp<SkSurface>& surface)
902 // Pre-fill the surface with deterministic garbage.
903 SkBitmap bitmap;
904 bitmap.allocN32Pixels(2, 2);
905 SkPMColor* scanline;
906 scanline = bitmap.getAddr32(0, 0);
907 *scanline++ = SkPreMultiplyARGB(0xFF, 0xBF, 0x80, 0x40);
908 *scanline++ = SkPreMultiplyARGB(0xFF, 0x40, 0x80, 0xBF);
909 scanline = bitmap.getAddr32(0, 1);
910 *scanline++ = SkPreMultiplyARGB(0xFF, 0xE3, 0x5C, 0x13);
911 *scanline++ = SkPreMultiplyARGB(0xFF, 0x13, 0x5C, 0xE3);
912 bitmap.setImmutable();
913 SkPaint paint;
914 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
915 paint.setShader(
916 bitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()));
917 surface->getCanvas()->drawPaint(paint);
919 #endif
921 } // namespace
923 #endif // HAVE_FEATURE_SKIA
925 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */