android: Update app icon to new startcenter icon
[LibreOffice.git] / vcl / skia / SkiaHelper.cxx
blob4a83aa1f61376a2908f4b8ff20f76e9860a44cac
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 <string_view>
14 #include <vcl/skia/SkiaHelper.hxx>
16 #if !HAVE_FEATURE_SKIA
18 namespace SkiaHelper
20 bool isVCLSkiaEnabled() { return false; }
21 bool isAlphaMaskBlendingEnabled() { return false; }
23 } // namespace
25 #else
27 #include <rtl/bootstrap.hxx>
28 #include <vcl/svapp.hxx>
29 #include <desktop/crashreport.hxx>
30 #include <officecfg/Office/Common.hxx>
31 #include <watchdog.hxx>
32 #include <skia/zone.hxx>
33 #include <sal/log.hxx>
34 #include <driverblocklist.hxx>
35 #include <skia/utils.hxx>
36 #include <config_folders.h>
37 #include <osl/file.hxx>
38 #include <tools/stream.hxx>
39 #include <list>
40 #include <o3tl/lru_map.hxx>
42 #include <SkBitmap.h>
43 #include <SkCanvas.h>
44 #include <SkEncodedImageFormat.h>
45 #include <SkPaint.h>
46 #include <SkSurface.h>
47 #include <SkGraphics.h>
48 #include <GrDirectContext.h>
49 #include <SkRuntimeEffect.h>
50 #include <SkOpts_spi.h>
51 #include <skia_compiler.hxx>
52 #include <skia_opts.hxx>
53 #include <tools/sk_app/VulkanWindowContext.h>
54 #include <tools/sk_app/MetalWindowContext.h>
55 #include <fstream>
57 #ifdef SK_METAL
58 #ifdef MACOSX
59 #include <quartz/cgutils.h>
60 #endif
61 #endif
63 namespace SkiaHelper
65 static OUString getCacheFolder()
67 OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
68 "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
69 rtl::Bootstrap::expandMacros(url);
70 osl::Directory::create(url);
71 return url;
74 static void writeToLog(SvStream& stream, const char* key, const char* value)
76 stream.WriteOString(key);
77 stream.WriteOString(": ");
78 stream.WriteOString(value);
79 stream.WriteChar('\n');
82 OUString readLog()
84 SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::READ);
86 OUString sResult;
87 OString sLine;
88 while (logFile.ReadLine(sLine))
89 sResult += OStringToOUString(sLine, RTL_TEXTENCODING_UTF8) + "\n";
91 return sResult;
94 uint32_t vendorId = 0;
96 #ifdef SK_VULKAN
97 static void writeToLog(SvStream& stream, const char* key, std::u16string_view value)
99 writeToLog(stream, key, OUStringToOString(value, RTL_TEXTENCODING_UTF8).getStr());
102 static OUString getDenylistFile()
104 OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
105 rtl::Bootstrap::expandMacros(url);
107 return url + "/skia/skia_denylist_vulkan.xml";
110 static uint32_t driverVersion = 0;
112 static OUString versionAsString(uint32_t version)
114 return OUString::number(version >> 22) + "." + OUString::number((version >> 12) & 0x3ff) + "."
115 + OUString::number(version & 0xfff);
118 static std::string_view vendorAsString(uint32_t vendor)
120 return DriverBlocklist::GetVendorNameFromId(vendor);
123 // Note that this function also logs system information about Vulkan.
124 static bool isVulkanDenylisted(const VkPhysicalDeviceProperties& props)
126 static const char* const types[]
127 = { "other", "integrated", "discrete", "virtual", "cpu", "??" }; // VkPhysicalDeviceType
128 driverVersion = props.driverVersion;
129 vendorId = props.vendorID;
130 OUString vendorIdStr = "0x" + OUString::number(props.vendorID, 16);
131 OUString deviceIdStr = "0x" + OUString::number(props.deviceID, 16);
132 OUString driverVersionString = versionAsString(driverVersion);
133 OUString apiVersion = versionAsString(props.apiVersion);
134 const char* deviceType = types[std::min<unsigned>(props.deviceType, SAL_N_ELEMENTS(types) - 1)];
136 CrashReporter::addKeyValue("VulkanVendor", vendorIdStr, CrashReporter::AddItem);
137 CrashReporter::addKeyValue("VulkanDevice", deviceIdStr, CrashReporter::AddItem);
138 CrashReporter::addKeyValue("VulkanAPI", apiVersion, CrashReporter::AddItem);
139 CrashReporter::addKeyValue("VulkanDriver", driverVersionString, CrashReporter::AddItem);
140 CrashReporter::addKeyValue("VulkanDeviceType", OUString::createFromAscii(deviceType),
141 CrashReporter::AddItem);
142 CrashReporter::addKeyValue("VulkanDeviceName", OUString::createFromAscii(props.deviceName),
143 CrashReporter::Write);
145 SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
146 writeToLog(logFile, "RenderMethod", "vulkan");
147 writeToLog(logFile, "Vendor", vendorIdStr);
148 writeToLog(logFile, "Device", deviceIdStr);
149 writeToLog(logFile, "API", apiVersion);
150 writeToLog(logFile, "Driver", driverVersionString);
151 writeToLog(logFile, "DeviceType", deviceType);
152 writeToLog(logFile, "DeviceName", props.deviceName);
154 SAL_INFO("vcl.skia",
155 "Vulkan API version: " << apiVersion << ", driver version: " << driverVersionString
156 << ", vendor: " << vendorIdStr << " ("
157 << vendorAsString(vendorId) << "), device: " << deviceIdStr
158 << ", type: " << deviceType << ", name: " << props.deviceName);
159 bool denylisted
160 = DriverBlocklist::IsDeviceBlocked(getDenylistFile(), DriverBlocklist::VersionType::Vulkan,
161 driverVersionString, vendorIdStr, deviceIdStr);
162 writeToLog(logFile, "Denylisted", denylisted ? "yes" : "no");
163 return denylisted;
165 #endif
167 #ifdef SK_METAL
168 static void writeSkiaMetalInfo()
170 SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
171 writeToLog(logFile, "RenderMethod", "metal");
173 #endif
175 static void writeSkiaRasterInfo()
177 SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
178 writeToLog(logFile, "RenderMethod", "raster");
179 // Log compiler, Skia works best when compiled with Clang.
180 writeToLog(logFile, "Compiler", skia_compiler_name());
183 #if defined(SK_VULKAN) || defined(SK_METAL)
184 static std::unique_ptr<sk_app::WindowContext> getTemporaryWindowContext();
185 #endif
187 static void checkDeviceDenylisted(bool blockDisable = false)
189 static bool done = false;
190 if (done)
191 return;
193 SkiaZone zone;
195 bool useRaster = false;
196 switch (renderMethodToUse())
198 case RenderVulkan:
200 #ifdef SK_VULKAN
201 // First try if a GrDirectContext already exists.
202 std::unique_ptr<sk_app::WindowContext> temporaryWindowContext;
203 GrDirectContext* grDirectContext
204 = sk_app::VulkanWindowContext::getSharedGrDirectContext();
205 if (!grDirectContext)
207 // This function is called from isVclSkiaEnabled(), which
208 // may be called when deciding which X11 visual to use,
209 // and that visual is normally needed when creating
210 // Skia's VulkanWindowContext, which is needed for the GrDirectContext.
211 // Avoid the loop by creating a temporary WindowContext
212 // that will use the default X11 visual (that shouldn't matter
213 // for just finding out information about Vulkan) and destroying
214 // the temporary context will clean up again.
215 temporaryWindowContext = getTemporaryWindowContext();
216 grDirectContext = sk_app::VulkanWindowContext::getSharedGrDirectContext();
218 bool denylisted = true; // assume the worst
219 if (grDirectContext) // Vulkan was initialized properly
221 denylisted
222 = isVulkanDenylisted(sk_app::VulkanWindowContext::getPhysDeviceProperties());
223 SAL_INFO("vcl.skia", "Vulkan denylisted: " << denylisted);
225 else
226 SAL_INFO("vcl.skia", "Vulkan could not be initialized");
227 if (denylisted && !blockDisable)
229 disableRenderMethod(RenderVulkan);
230 useRaster = true;
232 #else
233 SAL_WARN("vcl.skia", "Vulkan support not built in");
234 (void)blockDisable;
235 useRaster = true;
236 #endif
237 break;
239 case RenderMetal:
241 #ifdef SK_METAL
242 // First try if a GrDirectContext already exists.
243 std::unique_ptr<sk_app::WindowContext> temporaryWindowContext;
244 GrDirectContext* grDirectContext = sk_app::getMetalSharedGrDirectContext();
245 if (!grDirectContext)
247 // Create a temporary window context just to get the GrDirectContext,
248 // as an initial test of Metal functionality.
249 temporaryWindowContext = getTemporaryWindowContext();
250 grDirectContext = sk_app::getMetalSharedGrDirectContext();
252 if (grDirectContext) // Metal was initialized properly
254 #ifdef MACOSX
255 if (!blockDisable && !DefaultMTLDeviceIsSupported())
257 SAL_INFO("vcl.skia", "Metal default device not supported");
258 disableRenderMethod(RenderMetal);
259 useRaster = true;
261 else
262 #endif
264 SAL_INFO("vcl.skia", "Using Skia Metal mode");
265 writeSkiaMetalInfo();
268 else
270 SAL_INFO("vcl.skia", "Metal could not be initialized");
271 disableRenderMethod(RenderMetal);
272 useRaster = true;
274 #else
275 SAL_WARN("vcl.skia", "Metal support not built in");
276 useRaster = true;
277 #endif
278 break;
280 case RenderRaster:
281 useRaster = true;
282 break;
284 if (useRaster)
286 SAL_INFO("vcl.skia", "Using Skia raster mode");
287 // software, never denylisted
288 writeSkiaRasterInfo();
290 done = true;
293 static bool skiaSupportedByBackend = false;
294 static bool supportsVCLSkia()
296 if (!skiaSupportedByBackend)
298 SAL_INFO("vcl.skia", "Skia not supported by VCL backend, disabling");
299 return false;
301 return getenv("SAL_DISABLESKIA") == nullptr;
304 static void initInternal();
306 bool isVCLSkiaEnabled()
309 * The !bSet part should only be called once! Changing the results in the same
310 * run will mix Skia and normal rendering.
313 static bool bSet = false;
314 static bool bEnable = false;
315 static bool bForceSkia = false;
317 // allow global disable when testing SystemPrimitiveRenderer since current Skia on Win does not
318 // harmonize with using Direct2D and D2DPixelProcessor2D
319 static const bool bTestSystemPrimitiveRenderer(
320 nullptr != std::getenv("TEST_SYSTEM_PRIMITIVE_RENDERER"));
321 if (bTestSystemPrimitiveRenderer)
322 return false;
324 // No hardware rendering, so no Skia
325 // TODO SKIA
326 if (Application::IsBitmapRendering())
327 return false;
329 if (bSet)
331 return bForceSkia || bEnable;
335 * There are a number of cases that these environment variables cover:
336 * * SAL_FORCESKIA forces Skia if disabled by UI options or denylisted
337 * * SAL_DISABLESKIA avoids the use of Skia regardless of any option
340 bSet = true;
341 bForceSkia = !!getenv("SAL_FORCESKIA") || officecfg::Office::Common::VCL::ForceSkia::get();
343 bool bRet = false;
344 bool bSupportsVCLSkia = supportsVCLSkia();
345 if (bForceSkia && bSupportsVCLSkia)
347 bRet = true;
348 initInternal();
349 // don't actually block if denylisted, but log it if enabled, and also get the vendor id
350 checkDeviceDenylisted(true);
352 else if (getenv("SAL_FORCEGL"))
354 // Skia usage is checked before GL usage, so if GL is forced (and Skia is not), do not
355 // enable Skia in order to allow GL.
356 bRet = false;
358 else if (bSupportsVCLSkia)
360 static bool bEnableSkiaEnv = !!getenv("SAL_ENABLESKIA");
362 bEnable = bEnableSkiaEnv;
364 if (officecfg::Office::Common::VCL::UseSkia::get())
365 bEnable = true;
367 // Force disable in safe mode
368 if (Application::IsSafeModeEnabled())
369 bEnable = false;
371 if (bEnable)
373 initInternal();
374 checkDeviceDenylisted(); // switch to raster if driver is denylisted
377 bRet = bEnable;
380 if (bRet)
381 WatchdogThread::start();
383 CrashReporter::addKeyValue("UseSkia", OUString::boolean(bRet), CrashReporter::Write);
385 return bRet;
388 bool isAlphaMaskBlendingEnabled() { return false; }
390 static RenderMethod methodToUse = RenderRaster;
392 static bool initRenderMethodToUse()
394 if (const char* env = getenv("SAL_SKIA"))
396 if (strcmp(env, "raster") == 0)
398 methodToUse = RenderRaster;
399 return true;
401 #ifdef MACOSX
402 if (strcmp(env, "metal") == 0)
404 methodToUse = RenderMetal;
405 return true;
407 #else
408 if (strcmp(env, "vulkan") == 0)
410 methodToUse = RenderVulkan;
411 return true;
413 #endif
414 SAL_WARN("vcl.skia", "Unrecognized value of SAL_SKIA");
415 abort();
417 methodToUse = RenderRaster;
418 if (officecfg::Office::Common::VCL::ForceSkiaRaster::get())
419 return true;
420 #ifdef SK_METAL
421 methodToUse = RenderMetal;
422 #endif
423 #ifdef SK_VULKAN
424 methodToUse = RenderVulkan;
425 #endif
426 return true;
429 RenderMethod renderMethodToUse()
431 static bool methodToUseInited = initRenderMethodToUse();
432 if (methodToUseInited) // Used just to ensure thread-safe one-time init.
433 return methodToUse;
434 abort();
437 void disableRenderMethod(RenderMethod method)
439 if (renderMethodToUse() != method)
440 return;
441 // Choose a fallback, right now always raster.
442 methodToUse = RenderRaster;
445 // If needed, we'll allocate one extra window context so that we have a valid GrDirectContext
446 // from Vulkan/MetalWindowContext.
447 static std::unique_ptr<sk_app::WindowContext> sharedWindowContext;
449 static std::unique_ptr<sk_app::WindowContext> (*createGpuWindowContextFunction)(bool) = nullptr;
450 static void setCreateGpuWindowContext(std::unique_ptr<sk_app::WindowContext> (*function)(bool))
452 createGpuWindowContextFunction = function;
455 GrDirectContext* getSharedGrDirectContext()
457 SkiaZone zone;
458 assert(renderMethodToUse() != RenderRaster);
459 if (sharedWindowContext)
460 return sharedWindowContext->directContext();
461 // TODO mutex?
462 // Set up the shared GrDirectContext from Skia's (patched) Vulkan/MetalWindowContext, if it's been
463 // already set up.
464 switch (renderMethodToUse())
466 case RenderVulkan:
467 #ifdef SK_VULKAN
468 if (GrDirectContext* context = sk_app::VulkanWindowContext::getSharedGrDirectContext())
469 return context;
470 #endif
471 break;
472 case RenderMetal:
473 #ifdef SK_METAL
474 if (GrDirectContext* context = sk_app::getMetalSharedGrDirectContext())
475 return context;
476 #endif
477 break;
478 case RenderRaster:
479 abort();
481 static bool done = false;
482 if (done)
483 return nullptr;
484 done = true;
485 if (createGpuWindowContextFunction == nullptr)
486 return nullptr; // not initialized properly (e.g. used from a VCL backend with no Skia support)
487 sharedWindowContext = createGpuWindowContextFunction(false);
488 GrDirectContext* grDirectContext
489 = sharedWindowContext ? sharedWindowContext->directContext() : nullptr;
490 if (grDirectContext)
491 return grDirectContext;
492 SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia",
493 "Cannot create Vulkan GPU context, falling back to Raster");
494 SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia",
495 "Cannot create Metal GPU context, falling back to Raster");
496 disableRenderMethod(renderMethodToUse());
497 return nullptr;
500 #if defined(SK_VULKAN) || defined(SK_METAL)
501 static std::unique_ptr<sk_app::WindowContext> getTemporaryWindowContext()
503 if (createGpuWindowContextFunction == nullptr)
504 return nullptr;
505 return createGpuWindowContextFunction(true);
507 #endif
509 static RenderMethod renderMethodToUseForSize(const SkISize& size)
511 // Do not use GPU for small surfaces. The problem is that due to the separate alpha hack
512 // we quite often may call GetBitmap() on VirtualDevice, which is relatively slow
513 // when the pixels need to be fetched from the GPU. And there are documents that use
514 // many tiny surfaces (bsc#1183308 for example), where this slowness adds up too much.
515 // This should be re-evaluated once the separate alpha hack is removed (SKIA_USE_BITMAP32)
516 // and we no longer (hopefully) fetch pixels that often.
517 if (size.width() <= 32 && size.height() <= 32)
518 return RenderRaster;
519 return renderMethodToUse();
522 sk_sp<SkSurface> createSkSurface(int width, int height, SkColorType type, SkAlphaType alpha)
524 SkiaZone zone;
525 assert(type == kN32_SkColorType || type == kAlpha_8_SkColorType);
526 sk_sp<SkSurface> surface;
527 switch (renderMethodToUseForSize({ width, height }))
529 case RenderVulkan:
530 case RenderMetal:
532 if (GrDirectContext* grDirectContext = getSharedGrDirectContext())
534 surface = SkSurface::MakeRenderTarget(grDirectContext, skgpu::Budgeted::kNo,
535 SkImageInfo::Make(width, height, type, alpha),
536 0, surfaceProps());
537 if (surface)
539 #ifdef DBG_UTIL
540 prefillSurface(surface);
541 #endif
542 return surface;
544 SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia",
545 "Cannot create Vulkan GPU offscreen surface, falling back to Raster");
546 SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia",
547 "Cannot create Metal GPU offscreen surface, falling back to Raster");
549 break;
551 default:
552 break;
554 // Create raster surface as a fallback.
555 surface = SkSurface::MakeRaster(SkImageInfo::Make(width, height, type, alpha), surfaceProps());
556 assert(surface);
557 if (surface)
559 #ifdef DBG_UTIL
560 prefillSurface(surface);
561 #endif
562 return surface;
564 // In non-debug builds we could return SkSurface::MakeNull() and try to cope with the situation,
565 // but that can lead to unnoticed data loss, so better fail clearly.
566 abort();
569 sk_sp<SkImage> createSkImage(const SkBitmap& bitmap)
571 SkiaZone zone;
572 assert(bitmap.colorType() == kN32_SkColorType || bitmap.colorType() == kAlpha_8_SkColorType);
573 switch (renderMethodToUseForSize(bitmap.dimensions()))
575 case RenderVulkan:
576 case RenderMetal:
578 if (GrDirectContext* grDirectContext = getSharedGrDirectContext())
580 sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(
581 grDirectContext, skgpu::Budgeted::kNo,
582 bitmap.info().makeAlphaType(kPremul_SkAlphaType), 0, surfaceProps());
583 if (surface)
585 SkPaint paint;
586 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
587 surface->getCanvas()->drawImage(bitmap.asImage(), 0, 0, SkSamplingOptions(),
588 &paint);
589 return makeCheckedImageSnapshot(surface);
591 // Try to fall back in non-debug builds.
592 SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia",
593 "Cannot create Vulkan GPU offscreen surface, falling back to Raster");
594 SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia",
595 "Cannot create Metal GPU offscreen surface, falling back to Raster");
597 break;
599 default:
600 break;
602 // Create raster image as a fallback.
603 sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
604 assert(image);
605 return image;
608 sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface)
610 sk_sp<SkImage> ret = surface->makeImageSnapshot();
611 assert(ret);
612 if (ret)
613 return ret;
614 abort();
617 sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface, const SkIRect& bounds)
619 sk_sp<SkImage> ret = surface->makeImageSnapshot(bounds);
620 assert(ret);
621 if (ret)
622 return ret;
623 abort();
626 namespace
628 // Image cache, for saving results of complex operations such as drawTransformedBitmap().
629 struct ImageCacheItem
631 OString key;
632 sk_sp<SkImage> image;
633 tools::Long size; // cost of the item
635 } //namespace
637 // LRU cache, last item is the least recently used. Hopefully there won't be that many items
638 // to require a hash/map. Using o3tl::lru_map would be simpler, but it doesn't support
639 // calculating cost of each item.
640 static std::list<ImageCacheItem> imageCache;
641 static tools::Long imageCacheSize = 0; // sum of all ImageCacheItem.size
643 void addCachedImage(const OString& key, sk_sp<SkImage> image)
645 static bool disabled = getenv("SAL_DISABLE_SKIA_CACHE") != nullptr;
646 if (disabled)
647 return;
648 tools::Long size = static_cast<tools::Long>(image->width()) * image->height()
649 * SkColorTypeBytesPerPixel(image->imageInfo().colorType());
650 imageCache.push_front({ key, image, size });
651 imageCacheSize += size;
652 SAL_INFO("vcl.skia.trace", "addcachedimage " << image << " :" << size << "/" << imageCacheSize);
653 const tools::Long maxSize = maxImageCacheSize();
654 while (imageCacheSize > maxSize)
656 assert(!imageCache.empty());
657 imageCacheSize -= imageCache.back().size;
658 SAL_INFO("vcl.skia.trace",
659 "least used removal " << imageCache.back().image << ":" << imageCache.back().size);
660 imageCache.pop_back();
664 sk_sp<SkImage> findCachedImage(const OString& key)
666 for (auto it = imageCache.begin(); it != imageCache.end(); ++it)
668 if (it->key == key)
670 sk_sp<SkImage> ret = it->image;
671 SAL_INFO("vcl.skia.trace", "findcachedimage " << key << " : " << it->image << " found");
672 imageCache.splice(imageCache.begin(), imageCache, it);
673 return ret;
676 SAL_INFO("vcl.skia.trace", "findcachedimage " << key << " not found");
677 return nullptr;
680 void removeCachedImage(sk_sp<SkImage> image)
682 for (auto it = imageCache.begin(); it != imageCache.end();)
684 if (it->image == image)
686 imageCacheSize -= it->size;
687 assert(imageCacheSize >= 0);
688 it = imageCache.erase(it);
690 else
691 ++it;
695 tools::Long maxImageCacheSize()
697 // Defaults to 4x 2000px 32bpp images, 64MiB.
698 return officecfg::Office::Common::Cache::Skia::ImageCacheSize::get();
701 static o3tl::lru_map<uint32_t, uint32_t> checksumCache(256);
703 static uint32_t computeSkPixmapChecksum(const SkPixmap& pixmap)
705 // Use uint32_t because that's what SkOpts::hash_fn() returns.
706 static_assert(std::is_same_v<uint32_t, decltype(SkOpts::hash_fn(nullptr, 0, 0))>);
707 const size_t dataRowBytes = pixmap.width() << pixmap.shiftPerPixel();
708 if (dataRowBytes == pixmap.rowBytes())
709 return SkOpts::hash_fn(pixmap.addr(), pixmap.height() * dataRowBytes, 0);
710 uint32_t sum = 0;
711 for (int row = 0; row < pixmap.height(); ++row)
712 sum = SkOpts::hash_fn(pixmap.addr(0, row), dataRowBytes, sum);
713 return sum;
716 uint32_t getSkImageChecksum(sk_sp<SkImage> image)
718 // Cache the checksums based on the uniqueID() (which should stay the same
719 // for the same image), because it may be still somewhat expensive.
720 uint32_t id = image->uniqueID();
721 auto it = checksumCache.find(id);
722 if (it != checksumCache.end())
723 return it->second;
724 SkPixmap pixmap;
725 if (!image->peekPixels(&pixmap))
726 abort(); // Fetching of GPU-based pixels is expensive, and shouldn't(?) be needed anyway.
727 uint32_t checksum = computeSkPixmapChecksum(pixmap);
728 checksumCache.insert({ id, checksum });
729 return checksum;
732 static sk_sp<SkBlender> invertBlender;
733 static sk_sp<SkBlender> xorBlender;
735 // This does the invert operation, i.e. result = color(255-R,255-G,255-B,A).
736 void setBlenderInvert(SkPaint* paint)
738 if (!invertBlender)
740 // Note that the colors are premultiplied, so '1 - dst.r' must be
741 // written as 'dst.a - dst.r', since premultiplied R is in the range (0-A).
742 const char* const diff = R"(
743 vec4 main( vec4 src, vec4 dst )
745 return vec4( dst.a - dst.r, dst.a - dst.g, dst.a - dst.b, dst.a );
748 auto effect = SkRuntimeEffect::MakeForBlender(SkString(diff));
749 if (!effect.effect)
751 SAL_WARN("vcl.skia",
752 "SKRuntimeEffect::MakeForBlender failed: " << effect.errorText.c_str());
753 abort();
755 invertBlender = effect.effect->makeBlender(nullptr);
757 paint->setBlender(invertBlender);
760 // This does the xor operation, i.e. bitwise xor of RGB values of both colors.
761 void setBlenderXor(SkPaint* paint)
763 if (!xorBlender)
765 // Note that the colors are premultiplied, converting to 0-255 range
766 // must also unpremultiply.
767 const char* const diff = R"(
768 vec4 main( vec4 src, vec4 dst )
770 return vec4(
771 float(int(src.r * src.a * 255.0) ^ int(dst.r * dst.a * 255.0)) / 255.0 / dst.a,
772 float(int(src.g * src.a * 255.0) ^ int(dst.g * dst.a * 255.0)) / 255.0 / dst.a,
773 float(int(src.b * src.a * 255.0) ^ int(dst.b * dst.a * 255.0)) / 255.0 / dst.a,
774 dst.a );
777 SkRuntimeEffect::Options opts;
778 // Skia does not allow binary operators in the default ES2Strict mode, but that's only
779 // because of OpenGL support. We don't use OpenGL, and it's safe for all modes that we do use.
780 // https://groups.google.com/g/skia-discuss/c/EPLuQbg64Kc/m/2uDXFIGhAwAJ
781 opts.maxVersionAllowed = SkSL::Version::k300;
782 auto effect = SkRuntimeEffect::MakeForBlender(SkString(diff), opts);
783 if (!effect.effect)
785 SAL_WARN("vcl.skia",
786 "SKRuntimeEffect::MakeForBlender failed: " << effect.errorText.c_str());
787 abort();
789 xorBlender = effect.effect->makeBlender(nullptr);
791 paint->setBlender(xorBlender);
794 static void initInternal()
796 // Set up all things needed for using Skia.
797 SkGraphics::Init();
798 SkLoOpts::Init();
801 void cleanup()
803 sharedWindowContext.reset();
804 imageCache.clear();
805 imageCacheSize = 0;
806 invertBlender.reset();
807 xorBlender.reset();
810 static SkSurfaceProps commonSurfaceProps;
811 const SkSurfaceProps* surfaceProps() { return &commonSurfaceProps; }
813 void setPixelGeometry(SkPixelGeometry pixelGeometry)
815 commonSurfaceProps = SkSurfaceProps(commonSurfaceProps.flags(), pixelGeometry);
818 // Skia should not be used from VCL backends that do not actually support it, as there will be setup missing.
819 // The code here (that is in the vcl lib) needs a function for creating Vulkan/Metal context that is
820 // usually available only in the backend libs.
821 void prepareSkia(std::unique_ptr<sk_app::WindowContext> (*createGpuWindowContext)(bool))
823 setCreateGpuWindowContext(createGpuWindowContext);
824 skiaSupportedByBackend = true;
827 void dump(const SkBitmap& bitmap, const char* file) { dump(SkImage::MakeFromBitmap(bitmap), file); }
829 void dump(const sk_sp<SkSurface>& surface, const char* file)
831 surface->getCanvas()->flush();
832 dump(makeCheckedImageSnapshot(surface), file);
835 void dump(const sk_sp<SkImage>& image, const char* file)
837 sk_sp<SkData> data = image->encodeToData(SkEncodedImageFormat::kPNG, 1);
838 std::ofstream ostream(file, std::ios::binary);
839 ostream.write(static_cast<const char*>(data->data()), data->size());
842 #ifdef DBG_UTIL
843 void prefillSurface(const sk_sp<SkSurface>& surface)
845 // Pre-fill the surface with deterministic garbage.
846 SkBitmap bitmap;
847 bitmap.allocN32Pixels(2, 2);
848 SkPMColor* scanline;
849 scanline = bitmap.getAddr32(0, 0);
850 *scanline++ = SkPreMultiplyARGB(0xFF, 0xBF, 0x80, 0x40);
851 *scanline++ = SkPreMultiplyARGB(0xFF, 0x40, 0x80, 0xBF);
852 scanline = bitmap.getAddr32(0, 1);
853 *scanline++ = SkPreMultiplyARGB(0xFF, 0xE3, 0x5C, 0x13);
854 *scanline++ = SkPreMultiplyARGB(0xFF, 0x13, 0x5C, 0xE3);
855 bitmap.setImmutable();
856 SkPaint paint;
857 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
858 paint.setShader(
859 bitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()));
860 surface->getCanvas()->drawPaint(paint);
862 #endif
864 } // namespace
866 #endif // HAVE_FEATURE_SKIA
868 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */