xref: /core/vcl/skia/SkiaHelper.cxx (revision 3c8af232)
11e9fae67SLuboš Luňák /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
21e9fae67SLuboš Luňák /*
31e9fae67SLuboš Luňák  * This file is part of the LibreOffice project.
41e9fae67SLuboš Luňák  *
51e9fae67SLuboš Luňák  * This Source Code Form is subject to the terms of the Mozilla Public
61e9fae67SLuboš Luňák  * License, v. 2.0. If a copy of the MPL was not distributed with this
71e9fae67SLuboš Luňák  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
81e9fae67SLuboš Luňák  */
91e9fae67SLuboš Luňák 
1035e471bbSStephan Bergmann #include <sal/config.h>
1135e471bbSStephan Bergmann 
12*3c8af232SMike Kaganski #include <config_features.h>
13*3c8af232SMike Kaganski 
1435e471bbSStephan Bergmann #include <string_view>
1535e471bbSStephan Bergmann 
161e9fae67SLuboš Luňák #include <vcl/skia/SkiaHelper.hxx>
171e9fae67SLuboš Luňák 
185c4f872aSLuboš Luňák #if !HAVE_FEATURE_SKIA
19ea5eb463SLuboš Luňák 
20ea5eb463SLuboš Luňák namespace SkiaHelper
21ea5eb463SLuboš Luňák {
22ea5eb463SLuboš Luňák bool isVCLSkiaEnabled() { return false; }
233c63cb12SPatrick Luby bool isAlphaMaskBlendingEnabled() { return false; }
24ea5eb463SLuboš Luňák 
25ea5eb463SLuboš Luňák } // namespace
265c4f872aSLuboš Luňák 
275c4f872aSLuboš Luňák #else
285c4f872aSLuboš Luňák 
292b702f74SLuboš Luňák #include <rtl/bootstrap.hxx>
302b702f74SLuboš Luňák #include <vcl/svapp.hxx>
312b702f74SLuboš Luňák #include <desktop/crashreport.hxx>
322b702f74SLuboš Luňák #include <officecfg/Office/Common.hxx>
332b702f74SLuboš Luňák #include <watchdog.hxx>
342b702f74SLuboš Luňák #include <skia/zone.hxx>
352b702f74SLuboš Luňák #include <sal/log.hxx>
362b702f74SLuboš Luňák #include <driverblocklist.hxx>
37ea5eb463SLuboš Luňák #include <skia/utils.hxx>
382b702f74SLuboš Luňák #include <config_folders.h>
39*3c8af232SMike Kaganski #include <config_skia.h>
40e3f5e49aSLuboš Luňák #include <osl/file.hxx>
41e3f5e49aSLuboš Luňák #include <tools/stream.hxx>
42fc0bff85SLuboš Luňák #include <list>
432c86b79eSLuboš Luňák #include <o3tl/lru_map.hxx>
44ea5eb463SLuboš Luňák 
459f5e5ea0SLuboš Luňák #include <SkBitmap.h>
465d4cc08cSLuboš Luňák #include <SkCanvas.h>
479f5e5ea0SLuboš Luňák #include <SkEncodedImageFormat.h>
485d4cc08cSLuboš Luňák #include <SkPaint.h>
498fede4e7SLuboš Luňák #include <SkSurface.h>
50a3020001SLuboš Luňák #include <SkGraphics.h>
51eaf4f6d3SLuboš Luňák #include <GrDirectContext.h>
52110fa313SLuboš Luňák #include <SkRuntimeEffect.h>
53f8c15850SNoel Grandin #include <SkStream.h>
54f8c15850SNoel Grandin #include <SkTileMode.h>
552eeb6822SLuboš Luňák #include <skia_compiler.hxx>
567a38f181SLuboš Luňák #include <skia_opts.hxx>
57f8c15850SNoel Grandin #if defined(MACOSX)
58f8c15850SNoel Grandin #include <premac.h>
59f8c15850SNoel Grandin #endif
60d4afd3aeSLuboš Luňák #include <tools/sk_app/VulkanWindowContext.h>
61d4afd3aeSLuboš Luňák #include <tools/sk_app/MetalWindowContext.h>
62f8c15850SNoel Grandin #if defined(MACOSX)
63f8c15850SNoel Grandin #include <postmac.h>
64f8c15850SNoel Grandin #endif
65f8c15850SNoel Grandin #include <src/core/SkOpts.h>
66f8c15850SNoel Grandin #include <src/core/SkChecksum.h>
67f8c15850SNoel Grandin #include <include/encode/SkPngEncoder.h>
68f8c15850SNoel Grandin #include <ganesh/SkSurfaceGanesh.h>
69f8c15850SNoel Grandin #if defined _MSC_VER
70f8c15850SNoel Grandin #pragma warning(disable : 4100) // "unreferenced formal parameter"
71f8c15850SNoel Grandin #pragma warning(disable : 4324) // "structure was padded due to alignment specifier"
72f8c15850SNoel Grandin #endif
73f8c15850SNoel Grandin #if defined __clang__
74f8c15850SNoel Grandin #pragma clang diagnostic push
75f8c15850SNoel Grandin #pragma clang diagnostic ignored "-Wunused-parameter"
76f8c15850SNoel Grandin #endif
77f8c15850SNoel Grandin #if defined __GNUC__ && !defined __clang__
78f8c15850SNoel Grandin #pragma GCC diagnostic push
796ed4c4a1SJustin Luth #pragma GCC diagnostic ignored "-Wunused-parameter"
80f8c15850SNoel Grandin #endif
81f8c15850SNoel Grandin #include <src/image/SkImage_Base.h>
82f8c15850SNoel Grandin #if defined __GNUC__ && !defined __clang__
83f8c15850SNoel Grandin #pragma GCC diagnostic pop
84f8c15850SNoel Grandin #endif
85f8c15850SNoel Grandin #if defined __clang__
86f8c15850SNoel Grandin #pragma clang diagnostic pop
87f8c15850SNoel Grandin #endif
88f8c15850SNoel Grandin 
898fede4e7SLuboš Luňák #include <fstream>
908fede4e7SLuboš Luňák 
91fe3fa169SPatrick Luby #ifdef SK_METAL
92fe3fa169SPatrick Luby #ifdef MACOSX
93fe3fa169SPatrick Luby #include <quartz/cgutils.h>
94fe3fa169SPatrick Luby #endif
95fe3fa169SPatrick Luby #endif
96fe3fa169SPatrick Luby 
97ea5eb463SLuboš Luňák namespace SkiaHelper
98ea5eb463SLuboš Luňák {
99e3f5e49aSLuboš Luňák static OUString getCacheFolder()
100e3f5e49aSLuboš Luňák {
101e3f5e49aSLuboš Luňák     OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER
102e3f5e49aSLuboš Luňák                  "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
103e3f5e49aSLuboš Luňák     rtl::Bootstrap::expandMacros(url);
104e3f5e49aSLuboš Luňák     osl::Directory::create(url);
105e3f5e49aSLuboš Luňák     return url;
106e3f5e49aSLuboš Luňák }
107e3f5e49aSLuboš Luňák 
108e3f5e49aSLuboš Luňák static void writeToLog(SvStream& stream, const char* key, const char* value)
109e3f5e49aSLuboš Luňák {
1105fe96b6dSMike Kaganski     stream.WriteOString(key);
1115fe96b6dSMike Kaganski     stream.WriteOString(": ");
1125fe96b6dSMike Kaganski     stream.WriteOString(value);
113e3f5e49aSLuboš Luňák     stream.WriteChar('\n');
114e3f5e49aSLuboš Luňák }
115e3f5e49aSLuboš Luňák 
1164356790bSHeiko Tietze OUString readLog()
1174356790bSHeiko Tietze {
1184356790bSHeiko Tietze     SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::READ);
1194356790bSHeiko Tietze 
1204356790bSHeiko Tietze     OUString sResult;
1214356790bSHeiko Tietze     OString sLine;
1224356790bSHeiko Tietze     while (logFile.ReadLine(sLine))
1234356790bSHeiko Tietze         sResult += OStringToOUString(sLine, RTL_TEXTENCODING_UTF8) + "\n";
1244356790bSHeiko Tietze 
1254356790bSHeiko Tietze     return sResult;
1264356790bSHeiko Tietze }
1274356790bSHeiko Tietze 
128d4afd3aeSLuboš Luňák uint32_t vendorId = 0;
129d4afd3aeSLuboš Luňák 
130d4afd3aeSLuboš Luňák #ifdef SK_VULKAN
13135e471bbSStephan Bergmann static void writeToLog(SvStream& stream, const char* key, std::u16string_view value)
132e3f5e49aSLuboš Luňák {
133e3f5e49aSLuboš Luňák     writeToLog(stream, key, OUStringToOString(value, RTL_TEXTENCODING_UTF8).getStr());
134e3f5e49aSLuboš Luňák }
135e3f5e49aSLuboš Luňák 
136d4afd3aeSLuboš Luňák static OUString getDenylistFile()
137d4afd3aeSLuboš Luňák {
138d4afd3aeSLuboš Luňák     OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER);
139d4afd3aeSLuboš Luňák     rtl::Bootstrap::expandMacros(url);
140d4afd3aeSLuboš Luňák 
141d4afd3aeSLuboš Luňák     return url + "/skia/skia_denylist_vulkan.xml";
142d4afd3aeSLuboš Luňák }
143d4afd3aeSLuboš Luňák 
144d4afd3aeSLuboš Luňák static uint32_t driverVersion = 0;
145d4afd3aeSLuboš Luňák 
146d4afd3aeSLuboš Luňák static OUString versionAsString(uint32_t version)
147d4afd3aeSLuboš Luňák {
148d4afd3aeSLuboš Luňák     return OUString::number(version >> 22) + "." + OUString::number((version >> 12) & 0x3ff) + "."
149d4afd3aeSLuboš Luňák            + OUString::number(version & 0xfff);
150d4afd3aeSLuboš Luňák }
151d4afd3aeSLuboš Luňák 
152d4afd3aeSLuboš Luňák static std::string_view vendorAsString(uint32_t vendor)
153d4afd3aeSLuboš Luňák {
154d4afd3aeSLuboš Luňák     return DriverBlocklist::GetVendorNameFromId(vendor);
155d4afd3aeSLuboš Luňák }
156d4afd3aeSLuboš Luňák 
157e3f5e49aSLuboš Luňák // Note that this function also logs system information about Vulkan.
158493ae7a6SThorsten Behrens static bool isVulkanDenylisted(const VkPhysicalDeviceProperties& props)
15900dfb826SLuboš Luňák {
1603081998aSLuboš Luňák     static const char* const types[]
1613081998aSLuboš Luňák         = { "other", "integrated", "discrete", "virtual", "cpu", "??" }; // VkPhysicalDeviceType
162e3f5e49aSLuboš Luňák     driverVersion = props.driverVersion;
1631127a8deSLuboš Luňák     vendorId = props.vendorID;
1641127a8deSLuboš Luňák     OUString vendorIdStr = "0x" + OUString::number(props.vendorID, 16);
1651127a8deSLuboš Luňák     OUString deviceIdStr = "0x" + OUString::number(props.deviceID, 16);
166e3f5e49aSLuboš Luňák     OUString driverVersionString = versionAsString(driverVersion);
167e3f5e49aSLuboš Luňák     OUString apiVersion = versionAsString(props.apiVersion);
168e3f5e49aSLuboš Luňák     const char* deviceType = types[std::min<unsigned>(props.deviceType, SAL_N_ELEMENTS(types) - 1)];
169e3f5e49aSLuboš Luňák 
170e3f5e49aSLuboš Luňák     CrashReporter::addKeyValue("VulkanVendor", vendorIdStr, CrashReporter::AddItem);
171e3f5e49aSLuboš Luňák     CrashReporter::addKeyValue("VulkanDevice", deviceIdStr, CrashReporter::AddItem);
172e3f5e49aSLuboš Luňák     CrashReporter::addKeyValue("VulkanAPI", apiVersion, CrashReporter::AddItem);
173e3f5e49aSLuboš Luňák     CrashReporter::addKeyValue("VulkanDriver", driverVersionString, CrashReporter::AddItem);
174e3f5e49aSLuboš Luňák     CrashReporter::addKeyValue("VulkanDeviceType", OUString::createFromAscii(deviceType),
175e3f5e49aSLuboš Luňák                                CrashReporter::AddItem);
176e3f5e49aSLuboš Luňák     CrashReporter::addKeyValue("VulkanDeviceName", OUString::createFromAscii(props.deviceName),
177e3f5e49aSLuboš Luňák                                CrashReporter::Write);
178e3f5e49aSLuboš Luňák 
1792eeb6822SLuboš Luňák     SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
180e3f5e49aSLuboš Luňák     writeToLog(logFile, "RenderMethod", "vulkan");
181e3f5e49aSLuboš Luňák     writeToLog(logFile, "Vendor", vendorIdStr);
182e3f5e49aSLuboš Luňák     writeToLog(logFile, "Device", deviceIdStr);
183e3f5e49aSLuboš Luňák     writeToLog(logFile, "API", apiVersion);
184e3f5e49aSLuboš Luňák     writeToLog(logFile, "Driver", driverVersionString);
185e3f5e49aSLuboš Luňák     writeToLog(logFile, "DeviceType", deviceType);
186e3f5e49aSLuboš Luňák     writeToLog(logFile, "DeviceName", props.deviceName);
187e3f5e49aSLuboš Luňák 
1883081998aSLuboš Luňák     SAL_INFO("vcl.skia",
189e3f5e49aSLuboš Luňák              "Vulkan API version: " << apiVersion << ", driver version: " << driverVersionString
190e3f5e49aSLuboš Luňák                                     << ", vendor: " << vendorIdStr << " ("
191e3f5e49aSLuboš Luňák                                     << vendorAsString(vendorId) << "), device: " << deviceIdStr
192e3f5e49aSLuboš Luňák                                     << ", type: " << deviceType << ", name: " << props.deviceName);
193bf62a890SLuboš Luňák     bool denylisted
194bf62a890SLuboš Luňák         = DriverBlocklist::IsDeviceBlocked(getDenylistFile(), DriverBlocklist::VersionType::Vulkan,
195bf62a890SLuboš Luňák                                            driverVersionString, vendorIdStr, deviceIdStr);
196493ae7a6SThorsten Behrens     writeToLog(logFile, "Denylisted", denylisted ? "yes" : "no");
197493ae7a6SThorsten Behrens     return denylisted;
198e3f5e49aSLuboš Luňák }
199d4afd3aeSLuboš Luňák #endif
200d4afd3aeSLuboš Luňák 
201d4afd3aeSLuboš Luňák #ifdef SK_METAL
202d4afd3aeSLuboš Luňák static void writeSkiaMetalInfo()
203d4afd3aeSLuboš Luňák {
204d4afd3aeSLuboš Luňák     SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
205d4afd3aeSLuboš Luňák     writeToLog(logFile, "RenderMethod", "metal");
206d4afd3aeSLuboš Luňák }
207d4afd3aeSLuboš Luňák #endif
208e3f5e49aSLuboš Luňák 
209e3f5e49aSLuboš Luňák static void writeSkiaRasterInfo()
210e3f5e49aSLuboš Luňák {
2112eeb6822SLuboš Luňák     SvFileStream logFile(getCacheFolder() + "/skia.log", StreamMode::WRITE | StreamMode::TRUNC);
212e3f5e49aSLuboš Luňák     writeToLog(logFile, "RenderMethod", "raster");
213e3f5e49aSLuboš Luňák     // Log compiler, Skia works best when compiled with Clang.
2142eeb6822SLuboš Luňák     writeToLog(logFile, "Compiler", skia_compiler_name());
2153081998aSLuboš Luňák }
21600dfb826SLuboš Luňák 
217bbf7dc39SLuboš Luňák #if defined(SK_VULKAN) || defined(SK_METAL)
218d4afd3aeSLuboš Luňák static std::unique_ptr<sk_app::WindowContext> getTemporaryWindowContext();
219d4afd3aeSLuboš Luňák #endif
220b275cce0SLuboš Luňák 
221493ae7a6SThorsten Behrens static void checkDeviceDenylisted(bool blockDisable = false)
2223081998aSLuboš Luňák {
2233081998aSLuboš Luňák     static bool done = false;
224f45ff1a7SNoel Grandin     if (done)
225f45ff1a7SNoel Grandin         return;
226f45ff1a7SNoel Grandin 
227f45ff1a7SNoel Grandin     SkiaZone zone;
2283081998aSLuboš Luňák 
229bbf7dc39SLuboš Luňák     bool useRaster = false;
230f45ff1a7SNoel Grandin     switch (renderMethodToUse())
231f45ff1a7SNoel Grandin     {
232f45ff1a7SNoel Grandin         case RenderVulkan:
2333081998aSLuboš Luňák         {
234d4afd3aeSLuboš Luňák #ifdef SK_VULKAN
235eaf4f6d3SLuboš Luňák             // First try if a GrDirectContext already exists.
236d4afd3aeSLuboš Luňák             std::unique_ptr<sk_app::WindowContext> temporaryWindowContext;
237d4afd3aeSLuboš Luňák             GrDirectContext* grDirectContext
238eaf4f6d3SLuboš Luňák                 = sk_app::VulkanWindowContext::getSharedGrDirectContext();
239d4afd3aeSLuboš Luňák             if (!grDirectContext)
2403081998aSLuboš Luňák             {
241f45ff1a7SNoel Grandin                 // This function is called from isVclSkiaEnabled(), which
242f45ff1a7SNoel Grandin                 // may be called when deciding which X11 visual to use,
243f45ff1a7SNoel Grandin                 // and that visual is normally needed when creating
244eaf4f6d3SLuboš Luňák                 // Skia's VulkanWindowContext, which is needed for the GrDirectContext.
245d4afd3aeSLuboš Luňák                 // Avoid the loop by creating a temporary WindowContext
246f45ff1a7SNoel Grandin                 // that will use the default X11 visual (that shouldn't matter
247f45ff1a7SNoel Grandin                 // for just finding out information about Vulkan) and destroying
248f45ff1a7SNoel Grandin                 // the temporary context will clean up again.
249d4afd3aeSLuboš Luňák                 temporaryWindowContext = getTemporaryWindowContext();
250d4afd3aeSLuboš Luňák                 grDirectContext = sk_app::VulkanWindowContext::getSharedGrDirectContext();
2513081998aSLuboš Luňák             }
252f45ff1a7SNoel Grandin             bool denylisted = true; // assume the worst
253d4afd3aeSLuboš Luňák             if (grDirectContext) // Vulkan was initialized properly
254f45ff1a7SNoel Grandin             {
255f45ff1a7SNoel Grandin                 denylisted
256f45ff1a7SNoel Grandin                     = isVulkanDenylisted(sk_app::VulkanWindowContext::getPhysDeviceProperties());
257f45ff1a7SNoel Grandin                 SAL_INFO("vcl.skia", "Vulkan denylisted: " << denylisted);
258f45ff1a7SNoel Grandin             }
259f45ff1a7SNoel Grandin             else
260f45ff1a7SNoel Grandin                 SAL_INFO("vcl.skia", "Vulkan could not be initialized");
261f45ff1a7SNoel Grandin             if (denylisted && !blockDisable)
262f45ff1a7SNoel Grandin             {
263f45ff1a7SNoel Grandin                 disableRenderMethod(RenderVulkan);
264bbf7dc39SLuboš Luňák                 useRaster = true;
265f45ff1a7SNoel Grandin             }
266d4afd3aeSLuboš Luňák #else
267d4afd3aeSLuboš Luňák             SAL_WARN("vcl.skia", "Vulkan support not built in");
268d4afd3aeSLuboš Luňák             (void)blockDisable;
269bbf7dc39SLuboš Luňák             useRaster = true;
270d4afd3aeSLuboš Luňák #endif
271bbf7dc39SLuboš Luňák             break;
2723081998aSLuboš Luňák         }
273d4afd3aeSLuboš Luňák         case RenderMetal:
274bbf7dc39SLuboš Luňák         {
275d4afd3aeSLuboš Luňák #ifdef SK_METAL
276bbf7dc39SLuboš Luňák             // First try if a GrDirectContext already exists.
277bbf7dc39SLuboš Luňák             std::unique_ptr<sk_app::WindowContext> temporaryWindowContext;
278bbf7dc39SLuboš Luňák             GrDirectContext* grDirectContext = sk_app::getMetalSharedGrDirectContext();
279bbf7dc39SLuboš Luňák             if (!grDirectContext)
280bbf7dc39SLuboš Luňák             {
281bbf7dc39SLuboš Luňák                 // Create a temporary window context just to get the GrDirectContext,
282bbf7dc39SLuboš Luňák                 // as an initial test of Metal functionality.
283bbf7dc39SLuboš Luňák                 temporaryWindowContext = getTemporaryWindowContext();
284bbf7dc39SLuboš Luňák                 grDirectContext = sk_app::getMetalSharedGrDirectContext();
285bbf7dc39SLuboš Luňák             }
286bbf7dc39SLuboš Luňák             if (grDirectContext) // Metal was initialized properly
287bbf7dc39SLuboš Luňák             {
288fe3fa169SPatrick Luby #ifdef MACOSX
289fe3fa169SPatrick Luby                 if (!blockDisable && !DefaultMTLDeviceIsSupported())
290fe3fa169SPatrick Luby                 {
291fe3fa169SPatrick Luby                     SAL_INFO("vcl.skia", "Metal default device not supported");
292fe3fa169SPatrick Luby                     disableRenderMethod(RenderMetal);
293fe3fa169SPatrick Luby                     useRaster = true;
294fe3fa169SPatrick Luby                 }
295fe3fa169SPatrick Luby                 else
296fe3fa169SPatrick Luby #endif
297fe3fa169SPatrick Luby                 {
298fe3fa169SPatrick Luby                     SAL_INFO("vcl.skia", "Using Skia Metal mode");
299fe3fa169SPatrick Luby                     writeSkiaMetalInfo();
300fe3fa169SPatrick Luby                 }
301bbf7dc39SLuboš Luňák             }
302bbf7dc39SLuboš Luňák             else
303bbf7dc39SLuboš Luňák             {
304bbf7dc39SLuboš Luňák                 SAL_INFO("vcl.skia", "Metal could not be initialized");
305bbf7dc39SLuboš Luňák                 disableRenderMethod(RenderMetal);
306bbf7dc39SLuboš Luňák                 useRaster = true;
307bbf7dc39SLuboš Luňák             }
308d4afd3aeSLuboš Luňák #else
309d4afd3aeSLuboš Luňák             SAL_WARN("vcl.skia", "Metal support not built in");
310bbf7dc39SLuboš Luňák             useRaster = true;
311d4afd3aeSLuboš Luňák #endif
312bbf7dc39SLuboš Luňák             break;
313bbf7dc39SLuboš Luňák         }
314f45ff1a7SNoel Grandin         case RenderRaster:
315bbf7dc39SLuboš Luňák             useRaster = true;
316d4afd3aeSLuboš Luňák             break;
3173081998aSLuboš Luňák     }
318bbf7dc39SLuboš Luňák     if (useRaster)
319bbf7dc39SLuboš Luňák     {
320bbf7dc39SLuboš Luňák         SAL_INFO("vcl.skia", "Using Skia raster mode");
321bbf7dc39SLuboš Luňák         // software, never denylisted
322bbf7dc39SLuboš Luňák         writeSkiaRasterInfo();
323bbf7dc39SLuboš Luňák     }
324f45ff1a7SNoel Grandin     done = true;
32500dfb826SLuboš Luňák }
32600dfb826SLuboš Luňák 
327a3d3e076SLuboš Luňák static bool skiaSupportedByBackend = false;
328a3d3e076SLuboš Luňák static bool supportsVCLSkia()
329a3d3e076SLuboš Luňák {
330a3d3e076SLuboš Luňák     if (!skiaSupportedByBackend)
331a3d3e076SLuboš Luňák     {
332a3d3e076SLuboš Luňák         SAL_INFO("vcl.skia", "Skia not supported by VCL backend, disabling");
333a3d3e076SLuboš Luňák         return false;
334a3d3e076SLuboš Luňák     }
335a3d3e076SLuboš Luňák     return getenv("SAL_DISABLESKIA") == nullptr;
336a3d3e076SLuboš Luňák }
3373081998aSLuboš Luňák 
3386c58b01cSLuboš Luňák static void initInternal();
3396c58b01cSLuboš Luňák 
340ea5eb463SLuboš Luňák bool isVCLSkiaEnabled()
3411e9fae67SLuboš Luňák {
3421e9fae67SLuboš Luňák     /**
3431e9fae67SLuboš Luňák      * The !bSet part should only be called once! Changing the results in the same
3441e9fae67SLuboš Luňák      * run will mix Skia and normal rendering.
3451e9fae67SLuboš Luňák      */
3461e9fae67SLuboš Luňák 
3471e9fae67SLuboš Luňák     static bool bSet = false;
3481e9fae67SLuboš Luňák     static bool bEnable = false;
3491e9fae67SLuboš Luňák     static bool bForceSkia = false;
3501e9fae67SLuboš Luňák 
3515f29cb2fSArmin Le Grand (Allotropia)     // allow global disable when testing SystemPrimitiveRenderer since current Skia on Win does not
3525f29cb2fSArmin Le Grand (Allotropia)     // harmonize with using Direct2D and D2DPixelProcessor2D
3535f29cb2fSArmin Le Grand (Allotropia)     static const bool bTestSystemPrimitiveRenderer(
3545f29cb2fSArmin Le Grand (Allotropia)         nullptr != std::getenv("TEST_SYSTEM_PRIMITIVE_RENDERER"));
3555f29cb2fSArmin Le Grand (Allotropia)     if (bTestSystemPrimitiveRenderer)
3565f29cb2fSArmin Le Grand (Allotropia)         return false;
3575f29cb2fSArmin Le Grand (Allotropia) 
3581e9fae67SLuboš Luňák     if (bSet)
3591e9fae67SLuboš Luňák     {
3601e9fae67SLuboš Luňák         return bForceSkia || bEnable;
3611e9fae67SLuboš Luňák     }
3621e9fae67SLuboš Luňák 
3631e9fae67SLuboš Luňák     /*
3641e9fae67SLuboš Luňák      * There are a number of cases that these environment variables cover:
365493ae7a6SThorsten Behrens      *  * SAL_FORCESKIA forces Skia if disabled by UI options or denylisted
366a65cba8eSLuboš Luňák      *  * SAL_DISABLESKIA avoids the use of Skia regardless of any option
3671e9fae67SLuboš Luňák      */
3681e9fae67SLuboš Luňák 
3691e9fae67SLuboš Luňák     bSet = true;
37099bff8cfSLuboš Luňák     bForceSkia = !!getenv("SAL_FORCESKIA") || officecfg::Office::Common::VCL::ForceSkia::get();
3711e9fae67SLuboš Luňák 
3721e9fae67SLuboš Luňák     bool bRet = false;
37300dfb826SLuboš Luňák     bool bSupportsVCLSkia = supportsVCLSkia();
374a65cba8eSLuboš Luňák     if (bForceSkia && bSupportsVCLSkia)
3751e9fae67SLuboš Luňák     {
3761e9fae67SLuboš Luňák         bRet = true;
3776c58b01cSLuboš Luňák         initInternal();
378493ae7a6SThorsten Behrens         // don't actually block if denylisted, but log it if enabled, and also get the vendor id
379493ae7a6SThorsten Behrens         checkDeviceDenylisted(true);
3801e9fae67SLuboš Luňák     }
38167770fc4SLuboš Luňák     else if (getenv("SAL_FORCEGL"))
38267770fc4SLuboš Luňák     {
38367770fc4SLuboš Luňák         // Skia usage is checked before GL usage, so if GL is forced (and Skia is not), do not
38467770fc4SLuboš Luňák         // enable Skia in order to allow GL.
38567770fc4SLuboš Luňák         bRet = false;
38667770fc4SLuboš Luňák     }
3871e9fae67SLuboš Luňák     else if (bSupportsVCLSkia)
3881e9fae67SLuboš Luňák     {
3891e9fae67SLuboš Luňák         static bool bEnableSkiaEnv = !!getenv("SAL_ENABLESKIA");
3901e9fae67SLuboš Luňák 
3911e9fae67SLuboš Luňák         bEnable = bEnableSkiaEnv;
3921e9fae67SLuboš Luňák 
39399bff8cfSLuboš Luňák         if (officecfg::Office::Common::VCL::UseSkia::get())
3949245fd02STomoyuki Kubota             bEnable = true;
3951e9fae67SLuboš Luňák 
3961e9fae67SLuboš Luňák         // Force disable in safe mode
3971e9fae67SLuboš Luňák         if (Application::IsSafeModeEnabled())
3981e9fae67SLuboš Luňák             bEnable = false;
3991e9fae67SLuboš Luňák 
4003081998aSLuboš Luňák         if (bEnable)
401a3020001SLuboš Luňák         {
4026c58b01cSLuboš Luňák             initInternal();
403493ae7a6SThorsten Behrens             checkDeviceDenylisted(); // switch to raster if driver is denylisted
404a3020001SLuboš Luňák         }
4053081998aSLuboš Luňák 
4061e9fae67SLuboš Luňák         bRet = bEnable;
4071e9fae67SLuboš Luňák     }
4081e9fae67SLuboš Luňák 
4098a2a9d28SLuboš Luňák     if (bRet)
4108a2a9d28SLuboš Luňák         WatchdogThread::start();
4118a2a9d28SLuboš Luňák 
41299bff8cfSLuboš Luňák     CrashReporter::addKeyValue("UseSkia", OUString::boolean(bRet), CrashReporter::Write);
4131e9fae67SLuboš Luňák 
4141e9fae67SLuboš Luňák     return bRet;
4151e9fae67SLuboš Luňák }
4161e9fae67SLuboš Luňák 
4173c63cb12SPatrick Luby bool isAlphaMaskBlendingEnabled() { return false; }
4183c63cb12SPatrick Luby 
419ea5eb463SLuboš Luňák static RenderMethod methodToUse = RenderRaster;
420241730a7SLuboš Luňák 
421241730a7SLuboš Luňák static bool initRenderMethodToUse()
422241730a7SLuboš Luňák {
42391b4bfeeSMike Kaganski     if (Application::IsBitmapRendering())
42491b4bfeeSMike Kaganski     {
42591b4bfeeSMike Kaganski         methodToUse = RenderRaster;
42691b4bfeeSMike Kaganski         return true;
42791b4bfeeSMike Kaganski     }
42891b4bfeeSMike Kaganski 
429241730a7SLuboš Luňák     if (const char* env = getenv("SAL_SKIA"))
430241730a7SLuboš Luňák     {
431241730a7SLuboš Luňák         if (strcmp(env, "raster") == 0)
432241730a7SLuboš Luňák         {
433ea5eb463SLuboš Luňák             methodToUse = RenderRaster;
434241730a7SLuboš Luňák             return true;
435241730a7SLuboš Luňák         }
436d4afd3aeSLuboš Luňák #ifdef MACOSX
437d4afd3aeSLuboš Luňák         if (strcmp(env, "metal") == 0)
438d4afd3aeSLuboš Luňák         {
439d4afd3aeSLuboš Luňák             methodToUse = RenderMetal;
440d4afd3aeSLuboš Luňák             return true;
441d4afd3aeSLuboš Luňák         }
442d4afd3aeSLuboš Luňák #else
443e76bb4eaSLuboš Luňák         if (strcmp(env, "vulkan") == 0)
444e76bb4eaSLuboš Luňák         {
445e76bb4eaSLuboš Luňák             methodToUse = RenderVulkan;
446e76bb4eaSLuboš Luňák             return true;
447e76bb4eaSLuboš Luňák         }
448d4afd3aeSLuboš Luňák #endif
449e76bb4eaSLuboš Luňák         SAL_WARN("vcl.skia", "Unrecognized value of SAL_SKIA");
450e76bb4eaSLuboš Luňák         abort();
451241730a7SLuboš Luňák     }
452d4afd3aeSLuboš Luňák     methodToUse = RenderRaster;
45390b02dd4SLuboš Luňák     if (officecfg::Office::Common::VCL::ForceSkiaRaster::get())
45490b02dd4SLuboš Luňák         return true;
455d4afd3aeSLuboš Luňák #ifdef SK_METAL
456d4afd3aeSLuboš Luňák     methodToUse = RenderMetal;
457d4afd3aeSLuboš Luňák #endif
458d4afd3aeSLuboš Luňák #ifdef SK_VULKAN
459ea5eb463SLuboš Luňák     methodToUse = RenderVulkan;
460d4afd3aeSLuboš Luňák #endif
461241730a7SLuboš Luňák     return true;
462241730a7SLuboš Luňák }
463241730a7SLuboš Luňák 
464ea5eb463SLuboš Luňák RenderMethod renderMethodToUse()
465241730a7SLuboš Luňák {
466241730a7SLuboš Luňák     static bool methodToUseInited = initRenderMethodToUse();
467c2709d9fSLuboš Luňák     if (methodToUseInited) // Used just to ensure thread-safe one-time init.
468c2709d9fSLuboš Luňák         return methodToUse;
469c2709d9fSLuboš Luňák     abort();
470241730a7SLuboš Luňák }
471241730a7SLuboš Luňák 
472ea5eb463SLuboš Luňák void disableRenderMethod(RenderMethod method)
473241730a7SLuboš Luňák {
474241730a7SLuboš Luňák     if (renderMethodToUse() != method)
475241730a7SLuboš Luňák         return;
476241730a7SLuboš Luňák     // Choose a fallback, right now always raster.
477241730a7SLuboš Luňák     methodToUse = RenderRaster;
478241730a7SLuboš Luňák }
479241730a7SLuboš Luňák 
480d4afd3aeSLuboš Luňák // If needed, we'll allocate one extra window context so that we have a valid GrDirectContext
481d4afd3aeSLuboš Luňák // from Vulkan/MetalWindowContext.
482d4afd3aeSLuboš Luňák static std::unique_ptr<sk_app::WindowContext> sharedWindowContext;
483ea5eb463SLuboš Luňák 
484d4afd3aeSLuboš Luňák static std::unique_ptr<sk_app::WindowContext> (*createGpuWindowContextFunction)(bool) = nullptr;
485d4afd3aeSLuboš Luňák static void setCreateGpuWindowContext(std::unique_ptr<sk_app::WindowContext> (*function)(bool))
4863081998aSLuboš Luňák {
487d4afd3aeSLuboš Luňák     createGpuWindowContextFunction = function;
4883081998aSLuboš Luňák }
4893081998aSLuboš Luňák 
490eaf4f6d3SLuboš Luňák GrDirectContext* getSharedGrDirectContext()
491ea5eb463SLuboš Luňák {
4928a2a9d28SLuboš Luňák     SkiaZone zone;
493d4afd3aeSLuboš Luňák     assert(renderMethodToUse() != RenderRaster);
494d4afd3aeSLuboš Luňák     if (sharedWindowContext)
495d4afd3aeSLuboš Luňák         return sharedWindowContext->directContext();
496ea5eb463SLuboš Luňák     // TODO mutex?
497d4afd3aeSLuboš Luňák     // Set up the shared GrDirectContext from Skia's (patched) Vulkan/MetalWindowContext, if it's been
498ea5eb463SLuboš Luňák     // already set up.
499d4afd3aeSLuboš Luňák     switch (renderMethodToUse())
500ea5eb463SLuboš Luňák     {
501d4afd3aeSLuboš Luňák         case RenderVulkan:
502d4afd3aeSLuboš Luňák #ifdef SK_VULKAN
503d4afd3aeSLuboš Luňák             if (GrDirectContext* context = sk_app::VulkanWindowContext::getSharedGrDirectContext())
504d4afd3aeSLuboš Luňák                 return context;
505d4afd3aeSLuboš Luňák #endif
506d4afd3aeSLuboš Luňák             break;
507d4afd3aeSLuboš Luňák         case RenderMetal:
508d4afd3aeSLuboš Luňák #ifdef SK_METAL
509d4afd3aeSLuboš Luňák             if (GrDirectContext* context = sk_app::getMetalSharedGrDirectContext())
510d4afd3aeSLuboš Luňák                 return context;
511d4afd3aeSLuboš Luňák #endif
512d4afd3aeSLuboš Luňák             break;
513d4afd3aeSLuboš Luňák         case RenderRaster:
514d4afd3aeSLuboš Luňák             abort();
515ea5eb463SLuboš Luňák     }
5163081998aSLuboš Luňák     static bool done = false;
5173081998aSLuboš Luňák     if (done)
5183081998aSLuboš Luňák         return nullptr;
5193081998aSLuboš Luňák     done = true;
520d4afd3aeSLuboš Luňák     if (createGpuWindowContextFunction == nullptr)
521a3d3e076SLuboš Luňák         return nullptr; // not initialized properly (e.g. used from a VCL backend with no Skia support)
522d4afd3aeSLuboš Luňák     sharedWindowContext = createGpuWindowContextFunction(false);
523d4afd3aeSLuboš Luňák     GrDirectContext* grDirectContext
524d4afd3aeSLuboš Luňák         = sharedWindowContext ? sharedWindowContext->directContext() : nullptr;
525eaf4f6d3SLuboš Luňák     if (grDirectContext)
526eaf4f6d3SLuboš Luňák         return grDirectContext;
527d4afd3aeSLuboš Luňák     SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia",
528d4afd3aeSLuboš Luňák                 "Cannot create Vulkan GPU context, falling back to Raster");
529d4afd3aeSLuboš Luňák     SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia",
530d4afd3aeSLuboš Luňák                 "Cannot create Metal GPU context, falling back to Raster");
531d4afd3aeSLuboš Luňák     disableRenderMethod(renderMethodToUse());
532ea5eb463SLuboš Luňák     return nullptr;
533ea5eb463SLuboš Luňák }
534ea5eb463SLuboš Luňák 
535bbf7dc39SLuboš Luňák #if defined(SK_VULKAN) || defined(SK_METAL)
536d4afd3aeSLuboš Luňák static std::unique_ptr<sk_app::WindowContext> getTemporaryWindowContext()
537b275cce0SLuboš Luňák {
538d4afd3aeSLuboš Luňák     if (createGpuWindowContextFunction == nullptr)
539d4afd3aeSLuboš Luňák         return nullptr;
540d4afd3aeSLuboš Luňák     return createGpuWindowContextFunction(true);
541b275cce0SLuboš Luňák }
542d4afd3aeSLuboš Luňák #endif
543b275cce0SLuboš Luňák 
5448ca549ebSLuboš Luňák static RenderMethod renderMethodToUseForSize(const SkISize& size)
5458ca549ebSLuboš Luňák {
5468ca549ebSLuboš Luňák     // Do not use GPU for small surfaces. The problem is that due to the separate alpha hack
5478ca549ebSLuboš Luňák     // we quite often may call GetBitmap() on VirtualDevice, which is relatively slow
5488ca549ebSLuboš Luňák     // when the pixels need to be fetched from the GPU. And there are documents that use
5498ca549ebSLuboš Luňák     // many tiny surfaces (bsc#1183308 for example), where this slowness adds up too much.
5508ca549ebSLuboš Luňák     // This should be re-evaluated once the separate alpha hack is removed (SKIA_USE_BITMAP32)
5518ca549ebSLuboš Luňák     // and we no longer (hopefully) fetch pixels that often.
5528ca549ebSLuboš Luňák     if (size.width() <= 32 && size.height() <= 32)
5538ca549ebSLuboš Luňák         return RenderRaster;
5548ca549ebSLuboš Luňák     return renderMethodToUse();
5558ca549ebSLuboš Luňák }
5568ca549ebSLuboš Luňák 
5573f359a03SLuboš Luňák sk_sp<SkSurface> createSkSurface(int width, int height, SkColorType type, SkAlphaType alpha)
5588fede4e7SLuboš Luňák {
5598a2a9d28SLuboš Luňák     SkiaZone zone;
5608fede4e7SLuboš Luňák     assert(type == kN32_SkColorType || type == kAlpha_8_SkColorType);
5618fede4e7SLuboš Luňák     sk_sp<SkSurface> surface;
5628ca549ebSLuboš Luňák     switch (renderMethodToUseForSize({ width, height }))
5638fede4e7SLuboš Luňák     {
564715fe00aSLuboš Luňák         case RenderVulkan:
565d4afd3aeSLuboš Luňák         case RenderMetal:
5668fede4e7SLuboš Luňák         {
567eaf4f6d3SLuboš Luňák             if (GrDirectContext* grDirectContext = getSharedGrDirectContext())
5688fede4e7SLuboš Luňák             {
569f8c15850SNoel Grandin                 surface = SkSurfaces::RenderTarget(grDirectContext, skgpu::Budgeted::kNo,
570f8c15850SNoel Grandin                                                    SkImageInfo::Make(width, height, type, alpha), 0,
571f8c15850SNoel Grandin                                                    surfaceProps());
57219365e6eSLuboš Luňák                 if (surface)
57319365e6eSLuboš Luňák                 {
5748fede4e7SLuboš Luňák #ifdef DBG_UTIL
57519365e6eSLuboš Luňák                     prefillSurface(surface);
5768fede4e7SLuboš Luňák #endif
57719365e6eSLuboš Luňák                     return surface;
57819365e6eSLuboš Luňák                 }
579d4afd3aeSLuboš Luňák                 SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia",
580d4afd3aeSLuboš Luňák                             "Cannot create Vulkan GPU offscreen surface, falling back to Raster");
581d4afd3aeSLuboš Luňák                 SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia",
582d4afd3aeSLuboš Luňák                             "Cannot create Metal GPU offscreen surface, falling back to Raster");
5838fede4e7SLuboš Luňák             }
5848fede4e7SLuboš Luňák             break;
5858fede4e7SLuboš Luňák         }
5868fede4e7SLuboš Luňák         default:
5878fede4e7SLuboš Luňák             break;
5888fede4e7SLuboš Luňák     }
5898fede4e7SLuboš Luňák     // Create raster surface as a fallback.
590f8c15850SNoel Grandin     surface = SkSurfaces::Raster(SkImageInfo::Make(width, height, type, alpha), surfaceProps());
5918fede4e7SLuboš Luňák     assert(surface);
592797315f2SLuboš Luňák     if (surface)
593797315f2SLuboš Luňák     {
5948fede4e7SLuboš Luňák #ifdef DBG_UTIL
595797315f2SLuboš Luňák         prefillSurface(surface);
5968fede4e7SLuboš Luňák #endif
597797315f2SLuboš Luňák         return surface;
598797315f2SLuboš Luňák     }
599797315f2SLuboš Luňák     // In non-debug builds we could return SkSurface::MakeNull() and try to cope with the situation,
600797315f2SLuboš Luňák     // but that can lead to unnoticed data loss, so better fail clearly.
601797315f2SLuboš Luňák     abort();
6028fede4e7SLuboš Luňák }
6038fede4e7SLuboš Luňák 
604fafc059aSLuboš Luňák sk_sp<SkImage> createSkImage(const SkBitmap& bitmap)
605fafc059aSLuboš Luňák {
606fafc059aSLuboš Luňák     SkiaZone zone;
607fafc059aSLuboš Luňák     assert(bitmap.colorType() == kN32_SkColorType || bitmap.colorType() == kAlpha_8_SkColorType);
6088ca549ebSLuboš Luňák     switch (renderMethodToUseForSize(bitmap.dimensions()))
609fafc059aSLuboš Luňák     {
610715fe00aSLuboš Luňák         case RenderVulkan:
611d4afd3aeSLuboš Luňák         case RenderMetal:
612fafc059aSLuboš Luňák         {
613eaf4f6d3SLuboš Luňák             if (GrDirectContext* grDirectContext = getSharedGrDirectContext())
614fafc059aSLuboš Luňák             {
615f8c15850SNoel Grandin                 sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(
6169c9a711aSNoel Grandin                     grDirectContext, skgpu::Budgeted::kNo,
6171724b7a2SLuboš Luňák                     bitmap.info().makeAlphaType(kPremul_SkAlphaType), 0, surfaceProps());
61819365e6eSLuboš Luňák                 if (surface)
61919365e6eSLuboš Luňák                 {
62019365e6eSLuboš Luňák                     SkPaint paint;
62119365e6eSLuboš Luňák                     paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
622ad8bff9dSLuboš Luňák                     surface->getCanvas()->drawImage(bitmap.asImage(), 0, 0, SkSamplingOptions(),
623ad8bff9dSLuboš Luňák                                                     &paint);
624797315f2SLuboš Luňák                     return makeCheckedImageSnapshot(surface);
62519365e6eSLuboš Luňák                 }
62619365e6eSLuboš Luňák                 // Try to fall back in non-debug builds.
627d4afd3aeSLuboš Luňák                 SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia",
628d4afd3aeSLuboš Luňák                             "Cannot create Vulkan GPU offscreen surface, falling back to Raster");
629d4afd3aeSLuboš Luňák                 SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia",
630d4afd3aeSLuboš Luňák                             "Cannot create Metal GPU offscreen surface, falling back to Raster");
631fafc059aSLuboš Luňák             }
632fafc059aSLuboš Luňák             break;
633fafc059aSLuboš Luňák         }
634fafc059aSLuboš Luňák         default:
635fafc059aSLuboš Luňák             break;
636fafc059aSLuboš Luňák     }
637fafc059aSLuboš Luňák     // Create raster image as a fallback.
638f8c15850SNoel Grandin     sk_sp<SkImage> image = SkImages::RasterFromBitmap(bitmap);
639fafc059aSLuboš Luňák     assert(image);
640fafc059aSLuboš Luňák     return image;
641fafc059aSLuboš Luňák }
642fafc059aSLuboš Luňák 
643797315f2SLuboš Luňák sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface)
644797315f2SLuboš Luňák {
645797315f2SLuboš Luňák     sk_sp<SkImage> ret = surface->makeImageSnapshot();
646797315f2SLuboš Luňák     assert(ret);
647797315f2SLuboš Luňák     if (ret)
648797315f2SLuboš Luňák         return ret;
649797315f2SLuboš Luňák     abort();
650797315f2SLuboš Luňák }
651797315f2SLuboš Luňák 
652797315f2SLuboš Luňák sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface, const SkIRect& bounds)
653797315f2SLuboš Luňák {
654797315f2SLuboš Luňák     sk_sp<SkImage> ret = surface->makeImageSnapshot(bounds);
655797315f2SLuboš Luňák     assert(ret);
656797315f2SLuboš Luňák     if (ret)
657797315f2SLuboš Luňák         return ret;
658797315f2SLuboš Luňák     abort();
659797315f2SLuboš Luňák }
660797315f2SLuboš Luňák 
661fc0bff85SLuboš Luňák namespace
662fc0bff85SLuboš Luňák {
663fc0bff85SLuboš Luňák // Image cache, for saving results of complex operations such as drawTransformedBitmap().
664fc0bff85SLuboš Luňák struct ImageCacheItem
665fc0bff85SLuboš Luňák {
666fc0bff85SLuboš Luňák     OString key;
667fc0bff85SLuboš Luňák     sk_sp<SkImage> image;
668fae487b7SLuboš Luňák     tools::Long size; // cost of the item
669fc0bff85SLuboš Luňák };
670fc0bff85SLuboš Luňák } //namespace
671fc0bff85SLuboš Luňák 
672fc0bff85SLuboš Luňák // LRU cache, last item is the least recently used. Hopefully there won't be that many items
6732c86b79eSLuboš Luňák // to require a hash/map. Using o3tl::lru_map would be simpler, but it doesn't support
674fc0bff85SLuboš Luňák // calculating cost of each item.
6754e512171SNoel Grandin static std::list<ImageCacheItem> imageCache;
676fae487b7SLuboš Luňák static tools::Long imageCacheSize = 0; // sum of all ImageCacheItem.size
677fc0bff85SLuboš Luňák 
678fc0bff85SLuboš Luňák void addCachedImage(const OString& key, sk_sp<SkImage> image)
679fc0bff85SLuboš Luňák {
680fc0bff85SLuboš Luňák     static bool disabled = getenv("SAL_DISABLE_SKIA_CACHE") != nullptr;
681fc0bff85SLuboš Luňák     if (disabled)
682fc0bff85SLuboš Luňák         return;
683fae487b7SLuboš Luňák     tools::Long size = static_cast<tools::Long>(image->width()) * image->height()
684fae487b7SLuboš Luňák                        * SkColorTypeBytesPerPixel(image->imageInfo().colorType());
6854e512171SNoel Grandin     imageCache.push_front({ key, image, size });
686fc0bff85SLuboš Luňák     imageCacheSize += size;
687fc0bff85SLuboš Luňák     SAL_INFO("vcl.skia.trace", "addcachedimage " << image << " :" << size << "/" << imageCacheSize);
688fae487b7SLuboš Luňák     const tools::Long maxSize = maxImageCacheSize();
689fae487b7SLuboš Luňák     while (imageCacheSize > maxSize)
690fc0bff85SLuboš Luňák     {
6914e512171SNoel Grandin         assert(!imageCache.empty());
6924e512171SNoel Grandin         imageCacheSize -= imageCache.back().size;
6934e512171SNoel Grandin         SAL_INFO("vcl.skia.trace",
6944e512171SNoel Grandin                  "least used removal " << imageCache.back().image << ":" << imageCache.back().size);
6954e512171SNoel Grandin         imageCache.pop_back();
696fc0bff85SLuboš Luňák     }
697fc0bff85SLuboš Luňák }
698fc0bff85SLuboš Luňák 
699fc0bff85SLuboš Luňák sk_sp<SkImage> findCachedImage(const OString& key)
700fc0bff85SLuboš Luňák {
7014e512171SNoel Grandin     for (auto it = imageCache.begin(); it != imageCache.end(); ++it)
702fc0bff85SLuboš Luňák     {
7034e512171SNoel Grandin         if (it->key == key)
704fc0bff85SLuboš Luňák         {
7054e512171SNoel Grandin             sk_sp<SkImage> ret = it->image;
7064e512171SNoel Grandin             SAL_INFO("vcl.skia.trace", "findcachedimage " << key << " : " << it->image << " found");
7074e512171SNoel Grandin             imageCache.splice(imageCache.begin(), imageCache, it);
7084e512171SNoel Grandin             return ret;
709fc0bff85SLuboš Luňák         }
710fc0bff85SLuboš Luňák     }
7111b5bd34dSLuboš Luňák     SAL_INFO("vcl.skia.trace", "findcachedimage " << key << " not found");
712fc0bff85SLuboš Luňák     return nullptr;
713fc0bff85SLuboš Luňák }
714fc0bff85SLuboš Luňák 
715fc0bff85SLuboš Luňák void removeCachedImage(sk_sp<SkImage> image)
716fc0bff85SLuboš Luňák {
7174e512171SNoel Grandin     for (auto it = imageCache.begin(); it != imageCache.end();)
718fc0bff85SLuboš Luňák     {
719fc0bff85SLuboš Luňák         if (it->image == image)
720fc0bff85SLuboš Luňák         {
721fc0bff85SLuboš Luňák             imageCacheSize -= it->size;
722fc0bff85SLuboš Luňák             assert(imageCacheSize >= 0);
7234e512171SNoel Grandin             it = imageCache.erase(it);
724fc0bff85SLuboš Luňák         }
725fc0bff85SLuboš Luňák         else
726fc0bff85SLuboš Luňák             ++it;
727fc0bff85SLuboš Luňák     }
728fc0bff85SLuboš Luňák }
729fc0bff85SLuboš Luňák 
730fae487b7SLuboš Luňák tools::Long maxImageCacheSize()
731fae487b7SLuboš Luňák {
732fae487b7SLuboš Luňák     // Defaults to 4x 2000px 32bpp images, 64MiB.
733fae487b7SLuboš Luňák     return officecfg::Office::Common::Cache::Skia::ImageCacheSize::get();
734fae487b7SLuboš Luňák }
735fae487b7SLuboš Luňák 
7362c86b79eSLuboš Luňák static o3tl::lru_map<uint32_t, uint32_t> checksumCache(256);
7372c86b79eSLuboš Luňák 
738e369efa0SJulien Nabet static uint32_t computeSkPixmapChecksum(const SkPixmap& pixmap)
7392c86b79eSLuboš Luňák {
740f8c15850SNoel Grandin     // Use uint32_t because that's what SkChecksum::Hash32() returns.
741f8c15850SNoel Grandin     static_assert(std::is_same_v<uint32_t, decltype(SkChecksum::Hash32(nullptr, 0, 0))>);
7422c86b79eSLuboš Luňák     const size_t dataRowBytes = pixmap.width() << pixmap.shiftPerPixel();
7432c86b79eSLuboš Luňák     if (dataRowBytes == pixmap.rowBytes())
744f8c15850SNoel Grandin         return SkChecksum::Hash32(pixmap.addr(), pixmap.height() * dataRowBytes, 0);
7452c86b79eSLuboš Luňák     uint32_t sum = 0;
7462c86b79eSLuboš Luňák     for (int row = 0; row < pixmap.height(); ++row)
747f8c15850SNoel Grandin         sum = SkChecksum::Hash32(pixmap.addr(0, row), dataRowBytes, sum);
7482c86b79eSLuboš Luňák     return sum;
7492c86b79eSLuboš Luňák }
7502c86b79eSLuboš Luňák 
7512c86b79eSLuboš Luňák uint32_t getSkImageChecksum(sk_sp<SkImage> image)
7522c86b79eSLuboš Luňák {
7532c86b79eSLuboš Luňák     // Cache the checksums based on the uniqueID() (which should stay the same
7542c86b79eSLuboš Luňák     // for the same image), because it may be still somewhat expensive.
7552c86b79eSLuboš Luňák     uint32_t id = image->uniqueID();
7562c86b79eSLuboš Luňák     auto it = checksumCache.find(id);
7572c86b79eSLuboš Luňák     if (it != checksumCache.end())
7582c86b79eSLuboš Luňák         return it->second;
7592c86b79eSLuboš Luňák     SkPixmap pixmap;
7602c86b79eSLuboš Luňák     if (!image->peekPixels(&pixmap))
7612c86b79eSLuboš Luňák         abort(); // Fetching of GPU-based pixels is expensive, and shouldn't(?) be needed anyway.
7622c86b79eSLuboš Luňák     uint32_t checksum = computeSkPixmapChecksum(pixmap);
7632c86b79eSLuboš Luňák     checksumCache.insert({ id, checksum });
7642c86b79eSLuboš Luňák     return checksum;
7652c86b79eSLuboš Luňák }
7662c86b79eSLuboš Luňák 
767f7a628f8SLuboš Luňák static sk_sp<SkBlender> invertBlender;
768f7a628f8SLuboš Luňák static sk_sp<SkBlender> xorBlender;
769f7a628f8SLuboš Luňák 
770f7a628f8SLuboš Luňák // This does the invert operation, i.e. result = color(255-R,255-G,255-B,A).
771f7a628f8SLuboš Luňák void setBlenderInvert(SkPaint* paint)
772f7a628f8SLuboš Luňák {
773f7a628f8SLuboš Luňák     if (!invertBlender)
774f7a628f8SLuboš Luňák     {
775f7a628f8SLuboš Luňák         // Note that the colors are premultiplied, so '1 - dst.r' must be
776f7a628f8SLuboš Luňák         // written as 'dst.a - dst.r', since premultiplied R is in the range (0-A).
777f7a628f8SLuboš Luňák         const char* const diff = R"(
778f7a628f8SLuboš Luňák             vec4 main( vec4 src, vec4 dst )
779f7a628f8SLuboš Luňák             {
780f7a628f8SLuboš Luňák                 return vec4( dst.a - dst.r, dst.a - dst.g, dst.a - dst.b, dst.a );
781f7a628f8SLuboš Luňák             }
782f7a628f8SLuboš Luňák         )";
783f7a628f8SLuboš Luňák         auto effect = SkRuntimeEffect::MakeForBlender(SkString(diff));
784f7a628f8SLuboš Luňák         if (!effect.effect)
785f7a628f8SLuboš Luňák         {
786f7a628f8SLuboš Luňák             SAL_WARN("vcl.skia",
787f7a628f8SLuboš Luňák                      "SKRuntimeEffect::MakeForBlender failed: " << effect.errorText.c_str());
788f7a628f8SLuboš Luňák             abort();
789f7a628f8SLuboš Luňák         }
790f7a628f8SLuboš Luňák         invertBlender = effect.effect->makeBlender(nullptr);
791f7a628f8SLuboš Luňák     }
792f7a628f8SLuboš Luňák     paint->setBlender(invertBlender);
793f7a628f8SLuboš Luňák }
794f7a628f8SLuboš Luňák 
795f7a628f8SLuboš Luňák // This does the xor operation, i.e. bitwise xor of RGB values of both colors.
796f7a628f8SLuboš Luňák void setBlenderXor(SkPaint* paint)
797f7a628f8SLuboš Luňák {
798f7a628f8SLuboš Luňák     if (!xorBlender)
799f7a628f8SLuboš Luňák     {
800f7a628f8SLuboš Luňák         // Note that the colors are premultiplied, converting to 0-255 range
801f7a628f8SLuboš Luňák         // must also unpremultiply.
802f7a628f8SLuboš Luňák         const char* const diff = R"(
803f7a628f8SLuboš Luňák             vec4 main( vec4 src, vec4 dst )
804f7a628f8SLuboš Luňák             {
805f7a628f8SLuboš Luňák                 return vec4(
806f7a628f8SLuboš Luňák                     float(int(src.r * src.a * 255.0) ^ int(dst.r * dst.a * 255.0)) / 255.0 / dst.a,
807f7a628f8SLuboš Luňák                     float(int(src.g * src.a * 255.0) ^ int(dst.g * dst.a * 255.0)) / 255.0 / dst.a,
808f7a628f8SLuboš Luňák                     float(int(src.b * src.a * 255.0) ^ int(dst.b * dst.a * 255.0)) / 255.0 / dst.a,
809f7a628f8SLuboš Luňák                     dst.a );
810f7a628f8SLuboš Luňák             }
811f7a628f8SLuboš Luňák         )";
812f7a628f8SLuboš Luňák         SkRuntimeEffect::Options opts;
813f7a628f8SLuboš Luňák         // Skia does not allow binary operators in the default ES2Strict mode, but that's only
814f7a628f8SLuboš Luňák         // because of OpenGL support. We don't use OpenGL, and it's safe for all modes that we do use.
815f7a628f8SLuboš Luňák         // https://groups.google.com/g/skia-discuss/c/EPLuQbg64Kc/m/2uDXFIGhAwAJ
8169c9a711aSNoel Grandin         opts.maxVersionAllowed = SkSL::Version::k300;
817f7a628f8SLuboš Luňák         auto effect = SkRuntimeEffect::MakeForBlender(SkString(diff), opts);
818f7a628f8SLuboš Luňák         if (!effect.effect)
819f7a628f8SLuboš Luňák         {
820f7a628f8SLuboš Luňák             SAL_WARN("vcl.skia",
821f7a628f8SLuboš Luňák                      "SKRuntimeEffect::MakeForBlender failed: " << effect.errorText.c_str());
822f7a628f8SLuboš Luňák             abort();
823f7a628f8SLuboš Luňák         }
824f7a628f8SLuboš Luňák         xorBlender = effect.effect->makeBlender(nullptr);
825f7a628f8SLuboš Luňák     }
826f7a628f8SLuboš Luňák     paint->setBlender(xorBlender);
827f7a628f8SLuboš Luňák }
828f7a628f8SLuboš Luňák 
8296c58b01cSLuboš Luňák static void initInternal()
8306c58b01cSLuboš Luňák {
8316c58b01cSLuboš Luňák     // Set up all things needed for using Skia.
8326c58b01cSLuboš Luňák     SkGraphics::Init();
8336c58b01cSLuboš Luňák     SkLoOpts::Init();
8346c58b01cSLuboš Luňák }
8356c58b01cSLuboš Luňák 
836ea5eb463SLuboš Luňák void cleanup()
837ea5eb463SLuboš Luňák {
838d4afd3aeSLuboš Luňák     sharedWindowContext.reset();
8394e512171SNoel Grandin     imageCache.clear();
840fc0bff85SLuboš Luňák     imageCacheSize = 0;
841f7a628f8SLuboš Luňák     invertBlender.reset();
842f7a628f8SLuboš Luňák     xorBlender.reset();
843ea5eb463SLuboš Luňák }
844ea5eb463SLuboš Luňák 
8451724b7a2SLuboš Luňák static SkSurfaceProps commonSurfaceProps;
8461724b7a2SLuboš Luňák const SkSurfaceProps* surfaceProps() { return &commonSurfaceProps; }
8471724b7a2SLuboš Luňák 
8481724b7a2SLuboš Luňák void setPixelGeometry(SkPixelGeometry pixelGeometry)
8491724b7a2SLuboš Luňák {
8501724b7a2SLuboš Luňák     commonSurfaceProps = SkSurfaceProps(commonSurfaceProps.flags(), pixelGeometry);
8511724b7a2SLuboš Luňák }
8521724b7a2SLuboš Luňák 
853a3d3e076SLuboš Luňák // Skia should not be used from VCL backends that do not actually support it, as there will be setup missing.
854d4afd3aeSLuboš Luňák // The code here (that is in the vcl lib) needs a function for creating Vulkan/Metal context that is
855a3d3e076SLuboš Luňák // usually available only in the backend libs.
856d4afd3aeSLuboš Luňák void prepareSkia(std::unique_ptr<sk_app::WindowContext> (*createGpuWindowContext)(bool))
857a3d3e076SLuboš Luňák {
858d4afd3aeSLuboš Luňák     setCreateGpuWindowContext(createGpuWindowContext);
859a3d3e076SLuboš Luňák     skiaSupportedByBackend = true;
860a3d3e076SLuboš Luňák }
861a3d3e076SLuboš Luňák 
862f8c15850SNoel Grandin void dump(const SkBitmap& bitmap, const char* file)
863f8c15850SNoel Grandin {
864f8c15850SNoel Grandin     dump(SkImages::RasterFromBitmap(bitmap), file);
865f8c15850SNoel Grandin }
866f33b76b4SLuboš Luňák 
867f33b76b4SLuboš Luňák void dump(const sk_sp<SkSurface>& surface, const char* file)
868f33b76b4SLuboš Luňák {
869f33b76b4SLuboš Luňák     surface->getCanvas()->flush();
870f33b76b4SLuboš Luňák     dump(makeCheckedImageSnapshot(surface), file);
871f33b76b4SLuboš Luňák }
872f33b76b4SLuboš Luňák 
873f33b76b4SLuboš Luňák void dump(const sk_sp<SkImage>& image, const char* file)
874f33b76b4SLuboš Luňák {
875f8c15850SNoel Grandin     SkBitmap bm;
876f8c15850SNoel Grandin     if (!as_IB(image)->getROPixels(getSharedGrDirectContext(), &bm))
877f8c15850SNoel Grandin         return;
878f8c15850SNoel Grandin     SkPixmap pixmap;
879f8c15850SNoel Grandin     if (!bm.peekPixels(&pixmap))
880f8c15850SNoel Grandin         return;
881f8c15850SNoel Grandin     SkPngEncoder::Options opts;
882f8c15850SNoel Grandin     opts.fFilterFlags = SkPngEncoder::FilterFlag::kNone;
883f8c15850SNoel Grandin     opts.fZLibLevel = 1;
884f8c15850SNoel Grandin     SkDynamicMemoryWStream stream;
885f8c15850SNoel Grandin     if (!SkPngEncoder::Encode(&stream, pixmap, opts))
886f8c15850SNoel Grandin         return;
887f8c15850SNoel Grandin     sk_sp<SkData> data = stream.detachAsData();
888f33b76b4SLuboš Luňák     std::ofstream ostream(file, std::ios::binary);
889f33b76b4SLuboš Luňák     ostream.write(static_cast<const char*>(data->data()), data->size());
890f33b76b4SLuboš Luňák }
891f33b76b4SLuboš Luňák 
8928fede4e7SLuboš Luňák #ifdef DBG_UTIL
89322d94ce0SNoel void prefillSurface(const sk_sp<SkSurface>& surface)
8948fede4e7SLuboš Luňák {
8958fede4e7SLuboš Luňák     // Pre-fill the surface with deterministic garbage.
8968fede4e7SLuboš Luňák     SkBitmap bitmap;
8978fede4e7SLuboš Luňák     bitmap.allocN32Pixels(2, 2);
8988fede4e7SLuboš Luňák     SkPMColor* scanline;
8998fede4e7SLuboš Luňák     scanline = bitmap.getAddr32(0, 0);
9008fede4e7SLuboš Luňák     *scanline++ = SkPreMultiplyARGB(0xFF, 0xBF, 0x80, 0x40);
9018fede4e7SLuboš Luňák     *scanline++ = SkPreMultiplyARGB(0xFF, 0x40, 0x80, 0xBF);
9028fede4e7SLuboš Luňák     scanline = bitmap.getAddr32(0, 1);
9038fede4e7SLuboš Luňák     *scanline++ = SkPreMultiplyARGB(0xFF, 0xE3, 0x5C, 0x13);
9048fede4e7SLuboš Luňák     *scanline++ = SkPreMultiplyARGB(0xFF, 0x13, 0x5C, 0xE3);
9051f6cb097SLuboš Luňák     bitmap.setImmutable();
9068fede4e7SLuboš Luňák     SkPaint paint;
9078fede4e7SLuboš Luňák     paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
908ad8bff9dSLuboš Luňák     paint.setShader(
909ad8bff9dSLuboš Luňák         bitmap.makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()));
9108fede4e7SLuboš Luňák     surface->getCanvas()->drawPaint(paint);
9118fede4e7SLuboš Luňák }
9128fede4e7SLuboš Luňák #endif
9138fede4e7SLuboš Luňák 
914ea5eb463SLuboš Luňák } // namespace
915ea5eb463SLuboš Luňák 
9165c4f872aSLuboš Luňák #endif // HAVE_FEATURE_SKIA
9175c4f872aSLuboš Luňák 
9181e9fae67SLuboš Luňák /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
919