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 <string_view>
14 #include <vcl/skia/SkiaHelper.hxx>
16 #if !HAVE_FEATURE_SKIA
20 bool isVCLSkiaEnabled() { return false; }
21 bool isAlphaMaskBlendingEnabled() { return false; }
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>
40 #include <o3tl/lru_map.hxx>
44 #include <SkEncodedImageFormat.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>
59 #include <quartz/cgutils.h>
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
);
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');
84 SvFileStream
logFile(getCacheFolder() + "/skia.log", StreamMode::READ
);
88 while (logFile
.ReadLine(sLine
))
89 sResult
+= OStringToOUString(sLine
, RTL_TEXTENCODING_UTF8
) + "\n";
94 uint32_t vendorId
= 0;
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
);
155 "Vulkan API version: " << apiVersion
<< ", driver version: " << driverVersionString
156 << ", vendor: " << vendorIdStr
<< " ("
157 << vendorAsString(vendorId
) << "), device: " << deviceIdStr
158 << ", type: " << deviceType
<< ", name: " << props
.deviceName
);
160 = DriverBlocklist::IsDeviceBlocked(getDenylistFile(), DriverBlocklist::VersionType::Vulkan
,
161 driverVersionString
, vendorIdStr
, deviceIdStr
);
162 writeToLog(logFile
, "Denylisted", denylisted
? "yes" : "no");
168 static void writeSkiaMetalInfo()
170 SvFileStream
logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE
| StreamMode::TRUNC
);
171 writeToLog(logFile
, "RenderMethod", "metal");
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();
187 static void checkDeviceDenylisted(bool blockDisable
= false)
189 static bool done
= false;
195 bool useRaster
= false;
196 switch (renderMethodToUse())
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
222 = isVulkanDenylisted(sk_app::VulkanWindowContext::getPhysDeviceProperties());
223 SAL_INFO("vcl.skia", "Vulkan denylisted: " << denylisted
);
226 SAL_INFO("vcl.skia", "Vulkan could not be initialized");
227 if (denylisted
&& !blockDisable
)
229 disableRenderMethod(RenderVulkan
);
233 SAL_WARN("vcl.skia", "Vulkan support not built in");
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
255 if (!blockDisable
&& !DefaultMTLDeviceIsSupported())
257 SAL_INFO("vcl.skia", "Metal default device not supported");
258 disableRenderMethod(RenderMetal
);
264 SAL_INFO("vcl.skia", "Using Skia Metal mode");
265 writeSkiaMetalInfo();
270 SAL_INFO("vcl.skia", "Metal could not be initialized");
271 disableRenderMethod(RenderMetal
);
275 SAL_WARN("vcl.skia", "Metal support not built in");
286 SAL_INFO("vcl.skia", "Using Skia raster mode");
287 // software, never denylisted
288 writeSkiaRasterInfo();
293 static bool skiaSupportedByBackend
= false;
294 static bool supportsVCLSkia()
296 if (!skiaSupportedByBackend
)
298 SAL_INFO("vcl.skia", "Skia not supported by VCL backend, disabling");
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
)
324 // No hardware rendering, so no Skia
326 if (Application::IsBitmapRendering())
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
341 bForceSkia
= !!getenv("SAL_FORCESKIA") || officecfg::Office::Common::VCL::ForceSkia::get();
344 bool bSupportsVCLSkia
= supportsVCLSkia();
345 if (bForceSkia
&& bSupportsVCLSkia
)
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.
358 else if (bSupportsVCLSkia
)
360 static bool bEnableSkiaEnv
= !!getenv("SAL_ENABLESKIA");
362 bEnable
= bEnableSkiaEnv
;
364 if (officecfg::Office::Common::VCL::UseSkia::get())
367 // Force disable in safe mode
368 if (Application::IsSafeModeEnabled())
374 checkDeviceDenylisted(); // switch to raster if driver is denylisted
381 WatchdogThread::start();
383 CrashReporter::addKeyValue("UseSkia", OUString::boolean(bRet
), CrashReporter::Write
);
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
;
402 if (strcmp(env
, "metal") == 0)
404 methodToUse
= RenderMetal
;
408 if (strcmp(env
, "vulkan") == 0)
410 methodToUse
= RenderVulkan
;
414 SAL_WARN("vcl.skia", "Unrecognized value of SAL_SKIA");
417 methodToUse
= RenderRaster
;
418 if (officecfg::Office::Common::VCL::ForceSkiaRaster::get())
421 methodToUse
= RenderMetal
;
424 methodToUse
= RenderVulkan
;
429 RenderMethod
renderMethodToUse()
431 static bool methodToUseInited
= initRenderMethodToUse();
432 if (methodToUseInited
) // Used just to ensure thread-safe one-time init.
437 void disableRenderMethod(RenderMethod method
)
439 if (renderMethodToUse() != method
)
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()
458 assert(renderMethodToUse() != RenderRaster
);
459 if (sharedWindowContext
)
460 return sharedWindowContext
->directContext();
462 // Set up the shared GrDirectContext from Skia's (patched) Vulkan/MetalWindowContext, if it's been
464 switch (renderMethodToUse())
468 if (GrDirectContext
* context
= sk_app::VulkanWindowContext::getSharedGrDirectContext())
474 if (GrDirectContext
* context
= sk_app::getMetalSharedGrDirectContext())
481 static bool done
= false;
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;
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());
500 #if defined(SK_VULKAN) || defined(SK_METAL)
501 static std::unique_ptr
<sk_app::WindowContext
> getTemporaryWindowContext()
503 if (createGpuWindowContextFunction
== nullptr)
505 return createGpuWindowContextFunction(true);
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)
519 return renderMethodToUse();
522 sk_sp
<SkSurface
> createSkSurface(int width
, int height
, SkColorType type
, SkAlphaType alpha
)
525 assert(type
== kN32_SkColorType
|| type
== kAlpha_8_SkColorType
);
526 sk_sp
<SkSurface
> surface
;
527 switch (renderMethodToUseForSize({ width
, height
}))
532 if (GrDirectContext
* grDirectContext
= getSharedGrDirectContext())
534 surface
= SkSurface::MakeRenderTarget(grDirectContext
, skgpu::Budgeted::kNo
,
535 SkImageInfo::Make(width
, height
, type
, alpha
),
540 prefillSurface(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");
554 // Create raster surface as a fallback.
555 surface
= SkSurface::MakeRaster(SkImageInfo::Make(width
, height
, type
, alpha
), surfaceProps());
560 prefillSurface(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.
569 sk_sp
<SkImage
> createSkImage(const SkBitmap
& bitmap
)
572 assert(bitmap
.colorType() == kN32_SkColorType
|| bitmap
.colorType() == kAlpha_8_SkColorType
);
573 switch (renderMethodToUseForSize(bitmap
.dimensions()))
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());
586 paint
.setBlendMode(SkBlendMode::kSrc
); // set as is, including alpha
587 surface
->getCanvas()->drawImage(bitmap
.asImage(), 0, 0, SkSamplingOptions(),
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");
602 // Create raster image as a fallback.
603 sk_sp
<SkImage
> image
= SkImage::MakeFromBitmap(bitmap
);
608 sk_sp
<SkImage
> makeCheckedImageSnapshot(sk_sp
<SkSurface
> surface
)
610 sk_sp
<SkImage
> ret
= surface
->makeImageSnapshot();
617 sk_sp
<SkImage
> makeCheckedImageSnapshot(sk_sp
<SkSurface
> surface
, const SkIRect
& bounds
)
619 sk_sp
<SkImage
> ret
= surface
->makeImageSnapshot(bounds
);
628 // Image cache, for saving results of complex operations such as drawTransformedBitmap().
629 struct ImageCacheItem
632 sk_sp
<SkImage
> image
;
633 tools::Long size
; // cost of the item
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;
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
)
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
);
676 SAL_INFO("vcl.skia.trace", "findcachedimage " << key
<< " not found");
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
);
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);
711 for (int row
= 0; row
< pixmap
.height(); ++row
)
712 sum
= SkOpts::hash_fn(pixmap
.addr(0, row
), dataRowBytes
, 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())
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
});
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
)
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
));
752 "SKRuntimeEffect::MakeForBlender failed: " << effect
.errorText
.c_str());
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
)
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 )
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,
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
);
786 "SKRuntimeEffect::MakeForBlender failed: " << effect
.errorText
.c_str());
789 xorBlender
= effect
.effect
->makeBlender(nullptr);
791 paint
->setBlender(xorBlender
);
794 static void initInternal()
796 // Set up all things needed for using Skia.
803 sharedWindowContext
.reset();
806 invertBlender
.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());
843 void prefillSurface(const sk_sp
<SkSurface
>& surface
)
845 // Pre-fill the surface with deterministic garbage.
847 bitmap
.allocN32Pixels(2, 2);
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();
857 paint
.setBlendMode(SkBlendMode::kSrc
); // set as is, including alpha
859 bitmap
.makeShader(SkTileMode::kRepeat
, SkTileMode::kRepeat
, SkSamplingOptions()));
860 surface
->getCanvas()->drawPaint(paint
);
866 #endif // HAVE_FEATURE_SKIA
868 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */