1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
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
22 bool isVCLSkiaEnabled() { return false; }
23 bool isAlphaMaskBlendingEnabled() { return false; }
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>
43 #include <o3tl/lru_map.hxx>
47 #include <include/codec/SkEncodedImageFormat.h>
49 #include <SkSurface.h>
50 #include <SkGraphics.h>
51 #include <GrDirectContext.h>
52 #include <SkRuntimeEffect.h>
54 #include <SkTileMode.h>
55 #include <skia_compiler.hxx>
56 #include <skia_opts.hxx>
61 #include <tools/window/VulkanWindowContext.h>
64 #include <tools/window/MetalWindowContext.h>
69 #include <src/core/SkOpts.h>
70 #include <src/core/SkChecksum.h>
71 #include <include/encode/SkPngEncoder.h>
72 #include <ganesh/SkSurfaceGanesh.h>
74 #pragma warning(disable : 4100) // "unreferenced formal parameter"
75 #pragma warning(disable : 4324) // "structure was padded due to alignment specifier"
78 #pragma clang diagnostic push
79 #pragma clang diagnostic ignored "-Wunused-parameter"
81 #if defined __GNUC__ && !defined __clang__
82 #pragma GCC diagnostic push
83 #pragma GCC diagnostic ignored "-Wunused-parameter"
85 #include <src/image/SkImage_Base.h>
86 #if defined __GNUC__ && !defined __clang__
87 #pragma GCC diagnostic pop
90 #pragma clang diagnostic pop
97 #include <quartz/cgutils.h>
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
);
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');
122 SvFileStream
logFile(getCacheFolder() + "/skia.log", StreamMode::READ
);
126 while (logFile
.ReadLine(sLine
))
127 sResult
+= OStringToOUString(sLine
, RTL_TEXTENCODING_UTF8
) + "\n";
132 uint32_t vendorId
= 0;
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
);
193 "Vulkan API version: " << apiVersion
<< ", driver version: " << driverVersionString
194 << ", vendor: " << vendorIdStr
<< " ("
195 << vendorAsString(vendorId
) << "), device: " << deviceIdStr
196 << ", type: " << deviceType
<< ", name: " << props
.deviceName
);
198 = DriverBlocklist::IsDeviceBlocked(getDenylistFile(), DriverBlocklist::VersionType::Vulkan
,
199 driverVersionString
, vendorIdStr
, deviceIdStr
);
200 writeToLog(logFile
, "Denylisted", denylisted
? "yes" : "no");
206 static void writeSkiaMetalInfo()
208 SvFileStream
logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE
| StreamMode::TRUNC
);
209 writeToLog(logFile
, "RenderMethod", "metal");
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();
225 static void checkDeviceDenylisted(bool blockDisable
= false)
227 static bool done
= false;
233 bool useRaster
= false;
234 switch (renderMethodToUse())
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();
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
);
265 SAL_INFO("vcl.skia", "Vulkan could not be initialized");
266 if (denylisted
&& !blockDisable
)
268 disableRenderMethod(RenderVulkan
);
272 SAL_WARN("vcl.skia", "Vulkan support not built in");
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
294 if (!blockDisable
&& !DefaultMTLDeviceIsSupported())
296 SAL_INFO("vcl.skia", "Metal default device not supported");
297 disableRenderMethod(RenderMetal
);
303 SAL_INFO("vcl.skia", "Using Skia Metal mode");
304 writeSkiaMetalInfo();
309 SAL_INFO("vcl.skia", "Metal could not be initialized");
310 disableRenderMethod(RenderMetal
);
314 SAL_WARN("vcl.skia", "Metal support not built in");
325 SAL_INFO("vcl.skia", "Using Skia raster mode");
326 // software, never denylisted
327 writeSkiaRasterInfo();
332 static bool skiaSupportedByBackend
= false;
333 static bool supportsVCLSkia()
335 if (!skiaSupportedByBackend
)
337 SAL_INFO("vcl.skia", "Skia not supported by VCL backend, disabling");
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
)
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
375 bForceSkia
= !!getenv("SAL_FORCESKIA") || officecfg::Office::Common::VCL::ForceSkia::get();
378 bool bSupportsVCLSkia
= supportsVCLSkia();
379 if (bForceSkia
&& bSupportsVCLSkia
)
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.
392 else if (bSupportsVCLSkia
)
394 static bool bEnableSkiaEnv
= !!getenv("SAL_ENABLESKIA");
396 bEnable
= bEnableSkiaEnv
;
398 if (officecfg::Office::Common::VCL::UseSkia::get())
401 // Force disable in safe mode
402 if (Application::IsSafeModeEnabled())
408 checkDeviceDenylisted(); // switch to raster if driver is denylisted
415 WatchdogThread::start();
417 CrashReporter::addKeyValue(u
"UseSkia"_ustr
, OUString::boolean(bRet
), CrashReporter::Write
);
422 bool isAlphaMaskBlendingEnabled() { return false; }
424 static RenderMethod methodToUse
= RenderRaster
;
426 static bool initRenderMethodToUse()
428 if (Application::IsBitmapRendering())
430 methodToUse
= RenderRaster
;
434 if (const char* env
= getenv("SAL_SKIA"))
436 if (strcmp(env
, "raster") == 0)
438 methodToUse
= RenderRaster
;
442 if (strcmp(env
, "metal") == 0)
444 methodToUse
= RenderMetal
;
448 if (strcmp(env
, "vulkan") == 0)
450 methodToUse
= RenderVulkan
;
454 SAL_WARN("vcl.skia", "Unrecognized value of SAL_SKIA");
457 methodToUse
= RenderRaster
;
458 if (officecfg::Office::Common::VCL::ForceSkiaRaster::get())
461 methodToUse
= RenderMetal
;
464 methodToUse
= RenderVulkan
;
469 RenderMethod
renderMethodToUse()
471 static bool methodToUseInited
= initRenderMethodToUse();
472 if (methodToUseInited
) // Used just to ensure thread-safe one-time init.
477 void disableRenderMethod(RenderMethod method
)
479 if (renderMethodToUse() != method
)
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()
498 assert(renderMethodToUse() != RenderRaster
);
499 if (sharedWindowContext
)
500 return sharedWindowContext
->directContext();
502 // Set up the shared GrDirectContext from Skia's (patched) Vulkan/MetalWindowContext, if it's been
504 switch (renderMethodToUse())
508 if (GrDirectContext
* context
509 = skwindow::internal::VulkanWindowContext::getSharedGrDirectContext())
515 if (GrDirectContext
* context
= skwindow::internal::getMetalSharedGrDirectContext())
522 static bool done
= false;
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;
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());
541 #if defined(SK_VULKAN) || defined(SK_METAL)
542 static std::unique_ptr
<skwindow::WindowContext
> getTemporaryWindowContext()
544 if (createGpuWindowContextFunction
== nullptr)
546 return createGpuWindowContextFunction(true);
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)
560 return renderMethodToUse();
563 sk_sp
<SkSurface
> createSkSurface(int width
, int height
, SkColorType type
, SkAlphaType alpha
)
566 assert(type
== kN32_SkColorType
|| type
== kAlpha_8_SkColorType
);
567 sk_sp
<SkSurface
> surface
;
568 switch (renderMethodToUseForSize({ width
, height
}))
573 if (GrDirectContext
* grDirectContext
= getSharedGrDirectContext())
575 surface
= SkSurfaces::RenderTarget(grDirectContext
, skgpu::Budgeted::kNo
,
576 SkImageInfo::Make(width
, height
, type
, alpha
), 0,
581 prefillSurface(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");
595 // Create raster surface as a fallback.
596 surface
= SkSurfaces::Raster(SkImageInfo::Make(width
, height
, type
, alpha
), surfaceProps());
601 prefillSurface(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.
610 sk_sp
<SkImage
> createSkImage(const SkBitmap
& bitmap
)
613 assert(bitmap
.colorType() == kN32_SkColorType
|| bitmap
.colorType() == kAlpha_8_SkColorType
);
614 switch (renderMethodToUseForSize(bitmap
.dimensions()))
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());
627 paint
.setBlendMode(SkBlendMode::kSrc
); // set as is, including alpha
628 surface
->getCanvas()->drawImage(bitmap
.asImage(), 0, 0, SkSamplingOptions(),
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");
643 // Create raster image as a fallback.
644 sk_sp
<SkImage
> image
= SkImages::RasterFromBitmap(bitmap
);
649 sk_sp
<SkImage
> makeCheckedImageSnapshot(sk_sp
<SkSurface
> surface
)
651 sk_sp
<SkImage
> ret
= surface
->makeImageSnapshot();
658 sk_sp
<SkImage
> makeCheckedImageSnapshot(sk_sp
<SkSurface
> surface
, const SkIRect
& bounds
)
660 sk_sp
<SkImage
> ret
= surface
->makeImageSnapshot(bounds
);
669 // Image cache, for saving results of complex operations such as drawTransformedBitmap().
670 struct ImageCacheItem
673 sk_sp
<SkImage
> image
;
674 tools::Long size
; // cost of the item
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;
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
)
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
);
717 SAL_INFO("vcl.skia.trace", "findcachedimage " << key
<< " not found");
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
);
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);
752 for (int row
= 0; row
< pixmap
.height(); ++row
)
753 sum
= SkChecksum::Hash32(pixmap
.addr(0, row
), dataRowBytes
, 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())
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
});
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
)
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
));
793 "SKRuntimeEffect::MakeForBlender failed: " << effect
.errorText
.c_str());
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
)
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 )
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,
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
);
827 "SKRuntimeEffect::MakeForBlender failed: " << effect
.errorText
.c_str());
830 xorBlender
= effect
.effect
->makeBlender(nullptr);
832 paint
->setBlender(xorBlender
);
835 static void initInternal()
837 // Set up all things needed for using Skia.
844 sharedWindowContext
.reset();
847 invertBlender
.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
)
883 if (!as_IB(image
)->getROPixels(getSharedGrDirectContext(), &bm
))
886 if (!bm
.peekPixels(&pixmap
))
888 SkPngEncoder::Options opts
;
889 opts
.fFilterFlags
= SkPngEncoder::FilterFlag::kNone
;
891 SkDynamicMemoryWStream stream
;
892 if (!SkPngEncoder::Encode(&stream
, pixmap
, opts
))
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());
900 void prefillSurface(const sk_sp
<SkSurface
>& surface
)
902 // Pre-fill the surface with deterministic garbage.
904 bitmap
.allocN32Pixels(2, 2);
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();
914 paint
.setBlendMode(SkBlendMode::kSrc
); // set as is, including alpha
916 bitmap
.makeShader(SkTileMode::kRepeat
, SkTileMode::kRepeat
, SkSamplingOptions()));
917 surface
->getCanvas()->drawPaint(paint
);
923 #endif // HAVE_FEATURE_SKIA
925 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */