/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// returns old value staticbool setForceSkiaRaster(bool val)
{ constbool oldValue = officecfg::Office::Common::VCL::ForceSkiaRaster::get(); if (oldValue != val && !officecfg::Office::Common::VCL::ForceSkiaRaster::isReadOnly())
{ auto batch(comphelper::ConfigurationChanges::create());
officecfg::Office::Common::VCL::ForceSkiaRaster::set(val, batch);
batch->commit();
// make sure the change is written to the configuration if (auto xFlushable{ css::configuration::theDefaultProvider::get(
comphelper::getProcessComponentContext())
.query<css::util::XFlushable>() })
xFlushable->flush();
} return oldValue;
}
// Note that this function also logs system information about Vulkan. staticbool isVulkanDenylisted(const VkPhysicalDeviceProperties& props)
{ staticconstchar* const types[]
= { "other", "integrated", "discrete", "virtual", "cpu", "??" }; // VkPhysicalDeviceType
vendorId = props.vendorID;
OUString vendorIdStr = "0x" + OUString::number(props.vendorID, 16);
OUString deviceIdStr = "0x" + OUString::number(props.deviceID, 16);
OUString driverVersionString = versionAsString(props.driverVersion);
OUString apiVersion = versionAsString(props.apiVersion); constchar* deviceType = types[std::min<unsigned>(props.deviceType, SAL_N_ELEMENTS(types) - 1)];
bool useRaster = false; switch (renderMethodToUse())
{ case RenderVulkan:
{ #ifdef SK_VULKAN // Temporarily change config to force software rendering. If the following HW check // crashes, this config change will stay active, and will make sure to avoid use of // faulty HW/driver on the nest start constbool oldForceSkiaRasterValue = setForceSkiaRaster(true);
// First try if a GrDirectContext already exists.
std::unique_ptr<skwindow::WindowContext> temporaryWindowContext;
GrDirectContext* grDirectContext
= skwindow::internal::VulkanWindowContext::getSharedGrDirectContext(); if (!grDirectContext)
{ // This function is called from isVclSkiaEnabled(), which // may be called when deciding which X11 visual to use, // and that visual is normally needed when creating // Skia's VulkanWindowContext, which is needed for the GrDirectContext. // Avoid the loop by creating a temporary WindowContext // that will use the default X11 visual (that shouldn't matter // for just finding out information about Vulkan) and destroying // the temporary context will clean up again.
temporaryWindowContext = getTemporaryWindowContext();
grDirectContext
= skwindow::internal::VulkanWindowContext::getSharedGrDirectContext();
} bool denylisted = true; // assume the worst if (grDirectContext) // Vulkan was initialized properly
{
denylisted = isVulkanDenylisted(
skwindow::internal::VulkanWindowContext::getPhysDeviceProperties());
SAL_INFO("vcl.skia", "Vulkan denylisted: " << denylisted);
} else
SAL_INFO("vcl.skia", "Vulkan could not be initialized"); if (denylisted && !blockDisable)
{
forceRasterRenderMethod();
useRaster = true;
}
// The check succeeded; restore the original value
setForceSkiaRaster(oldForceSkiaRasterValue); #else
SAL_WARN("vcl.skia", "Vulkan support not built in");
(void)blockDisable;
useRaster = true; #endif break;
} case RenderMetal:
{ #ifdef SK_METAL // First try if a GrDirectContext already exists.
std::unique_ptr<skwindow::WindowContext> temporaryWindowContext;
GrDirectContext* grDirectContext = skwindow::internal::getMetalSharedGrDirectContext(); if (!grDirectContext)
{ // Create a temporary window context just to get the GrDirectContext, // as an initial test of Metal functionality.
temporaryWindowContext = getTemporaryWindowContext();
grDirectContext = skwindow::internal::getMetalSharedGrDirectContext();
} if (grDirectContext) // Metal was initialized properly
{ #ifdef MACOSX if (!blockDisable && !DefaultMTLDeviceIsSupported())
{
SAL_INFO("vcl.skia", "Metal default device not supported");
forceRasterRenderMethod();
useRaster = true;
} else #endif
{
SAL_INFO("vcl.skia", "Using Skia Metal mode");
writeSkiaMetalInfo();
}
} else
{
SAL_INFO("vcl.skia", "Metal could not be initialized");
forceRasterRenderMethod();
useRaster = true;
} #else
SAL_WARN("vcl.skia", "Metal support not built in");
useRaster = true; #endif break;
} case RenderRaster:
useRaster = true; break;
} if (useRaster)
{
SAL_INFO("vcl.skia", "Using Skia raster mode"); // software, never denylisted
writeSkiaRasterInfo();
}
done = true;
}
static std::atomic<bool> skiaSupportedByBackend = false; staticbool supportsVCLSkia()
{ if (skiaSupportedByBackend) returntrue;
SAL_INFO("vcl.skia", "Skia not supported by VCL backend, disabling"); returnfalse;
}
staticbool initVCLSkiaEnabled()
{ /** * Should only be called once! Changing the results in the same * run will mix Skia and normal rendering.
*/
// allow global disable when testing SystemPrimitiveRenderer since current Skia on Win does not // harmonize with using Direct2D and D2DPixelProcessor2D if (std::getenv("TEST_SYSTEM_PRIMITIVE_RENDERER") != nullptr) returnfalse;
/* * There are a number of cases that these environment variables cover: * * SAL_FORCESKIA forces Skia if disabled by UI options or denylisted * * SAL_DISABLESKIA avoids the use of Skia regardless of any option
*/
bRet = bForceSkia; // If not forced, don't enable in safe mode if (!bRet && !Application::IsSafeModeEnabled())
{
bRet = getenv("SAL_ENABLESKIA") != nullptr
|| officecfg::Office::Common::VCL::UseSkia::get();
}
if (bRet)
{ // Set up all things needed for using Skia.
SkGraphics::Init();
SkLoOpts::Init(); // if bForceSkia, don't actually block if denylisted, but log it if enabled, // and also get the vendor id; otherwise, switch to raster if driver is denylisted
checkDeviceDenylisted(bForceSkia);
WatchdogThread::start();
}
}
// If needed, we'll allocate one extra window context so that we have a valid GrDirectContext // from Vulkan/MetalWindowContext. static std::unique_ptr<skwindow::WindowContext> sharedWindowContext;
static RenderMethod renderMethodToUseForSize(const SkISize& size)
{ // Do not use GPU for small surfaces. The problem is that due to the separate alpha hack // we quite often may call GetBitmap() on VirtualDevice, which is relatively slow // when the pixels need to be fetched from the GPU. And there are documents that use // many tiny surfaces (bsc#1183308 for example), where this slowness adds up too much. // This should be re-evaluated once the separate alpha hack is removed (SKIA_USE_BITMAP32) // and we no longer (hopefully) fetch pixels that often. if (size.width() <= 32 && size.height() <= 32) return RenderRaster; return renderMethodToUse();
}
sk_sp<SkSurface> createSkSurface(int width, int height, SkColorType type, SkAlphaType alpha)
{
SkiaZone zone;
assert(type == kN32_SkColorType || type == kAlpha_8_SkColorType);
sk_sp<SkSurface> surface; switch (renderMethodToUseForSize({ width, height }))
{ case RenderVulkan: case RenderMetal:
{ if (GrDirectContext* grDirectContext = getSharedGrDirectContext())
{
surface = SkSurfaces::RenderTarget(grDirectContext, skgpu::Budgeted::kNo,
SkImageInfo::Make(width, height, type, alpha), 0,
surfaceProps()); if (surface)
{ #ifdef DBG_UTIL
prefillSurface(surface); #endif return surface;
}
SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia", "Cannot create Vulkan GPU offscreen surface, falling back to Raster");
SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia", "Cannot create Metal GPU offscreen surface, falling back to Raster");
} break;
} default: break;
} // Create raster surface as a fallback.
surface = SkSurfaces::Raster(SkImageInfo::Make(width, height, type, alpha), surfaceProps());
assert(surface); if (surface)
{ #ifdef DBG_UTIL
prefillSurface(surface); #endif return surface;
} // In non-debug builds we could return SkSurface::MakeNull() and try to cope with the situation, // but that can lead to unnoticed data loss, so better fail clearly.
abort();
}
sk_sp<SkImage> createSkImage(const SkBitmap& bitmap)
{
SkiaZone zone;
assert(bitmap.colorType() == kN32_SkColorType || bitmap.colorType() == kAlpha_8_SkColorType); switch (renderMethodToUseForSize(bitmap.dimensions()))
{ case RenderVulkan: case RenderMetal:
{ if (GrDirectContext* grDirectContext = getSharedGrDirectContext())
{
sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(
grDirectContext, skgpu::Budgeted::kNo,
bitmap.info().makeAlphaType(kPremul_SkAlphaType), 0, surfaceProps()); if (surface)
{
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
surface->getCanvas()->drawImage(bitmap.asImage(), 0, 0, SkSamplingOptions(),
&paint); return makeCheckedImageSnapshot(surface);
} // Try to fall back in non-debug builds.
SAL_WARN_IF(renderMethodToUse() == RenderVulkan, "vcl.skia", "Cannot create Vulkan GPU offscreen surface, falling back to Raster");
SAL_WARN_IF(renderMethodToUse() == RenderMetal, "vcl.skia", "Cannot create Metal GPU offscreen surface, falling back to Raster");
} break;
} default: break;
} // Create raster image as a fallback.
sk_sp<SkImage> image = SkImages::RasterFromBitmap(bitmap);
assert(image); return image;
}
sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface)
{
sk_sp<SkImage> ret = surface->makeImageSnapshot();
assert(ret); if (ret) return ret;
abort();
}
sk_sp<SkImage> makeCheckedImageSnapshot(sk_sp<SkSurface> surface, const SkIRect& bounds)
{
sk_sp<SkImage> ret = surface->makeImageSnapshot(bounds);
assert(ret); if (ret) return ret;
abort();
}
namespace
{ // Image cache, for saving results of complex operations such as drawTransformedBitmap(). struct ImageCacheItem
{
OString key;
sk_sp<SkImage> image;
tools::Long size; // cost of the item longlong keepInCacheUntilMilliseconds; // don't remove from cache before this timestamp
};
} //namespace
// LRU cache, last item is the least recently used. Hopefully there won't be that many items // to require a hash/map. Using o3tl::lru_map would be simpler, but it doesn't support // calculating cost of each item. static std::list<ImageCacheItem> imageCache; static tools::Long imageCacheSize = 0; // sum of all ImageCacheItem.size
// Related: tdf#166994 set the minimum amount of time that an image must // remain in the image cache to 5 seconds. 2 seconds appears to be enough // on macOS but set it to 5 seconds just to be safe. constlonglong imageMinKeepInCacheMilliseconds = 5000;
void addCachedImage(const OString& key, sk_sp<SkImage> image)
{ staticbool disabled = getenv("SAL_DISABLE_SKIA_CACHE") != nullptr; if (disabled) return;
tools::Long size = static_cast<tools::Long>(image->width()) * image->height()
* SkColorTypeBytesPerPixel(image->imageInfo().colorType()); auto time = std::chrono::steady_clock::now().time_since_epoch(); auto now = std::chrono::duration_cast<std::chrono::milliseconds>(time).count();
imageCache.push_front({ key, image, size, now + imageMinKeepInCacheMilliseconds });
imageCacheSize += size;
SAL_INFO("vcl.skia.trace", "addcachedimage " << image << " :" << size << "/" << imageCacheSize); const tools::Long maxSize = maxImageCacheSize(); while (imageCacheSize > maxSize)
{
assert(!imageCache.empty());
// tdf#166994 Don't remove image from cache too quickly // While an animation is running, the same images are drawn in a // repeating cycle but each frame is removed from the cache before // it is drawn again. // Blending a bitmap with an alpha channel can be very slow so // it is much faster to keep all of the frames in the image cache. // So, allow the image cache to temporarily increase in size by // deferring removal of an image if it was cached within the last // few seconds. if (now < imageCache.back().keepInCacheUntilMilliseconds) break;
static uint32_t computeSkPixmapChecksum(const SkPixmap& pixmap)
{ // Use uint32_t because that's what SkChecksum::Hash32() returns.
static_assert(std::is_same_v<uint32_t, decltype(SkChecksum::Hash32(nullptr, 0, 0))>); const size_t dataRowBytes = pixmap.width() << pixmap.shiftPerPixel(); if (dataRowBytes == pixmap.rowBytes()) return SkChecksum::Hash32(pixmap.addr(), pixmap.height() * dataRowBytes, 0);
uint32_t sum = 0; for (int row = 0; row < pixmap.height(); ++row)
sum = SkChecksum::Hash32(pixmap.addr(0, row), dataRowBytes, sum); return sum;
}
uint32_t getSkImageChecksum(sk_sp<SkImage> image)
{ // Cache the checksums based on the uniqueID() (which should stay the same // for the same image), because it may be still somewhat expensive.
uint32_t id = image->uniqueID(); auto it = checksumCache.find(id); if (it != checksumCache.end()) return it->second;
SkPixmap pixmap; if (!image->peekPixels(&pixmap))
abort(); // Fetching of GPU-based pixels is expensive, and shouldn't(?) be needed anyway.
uint32_t checksum = computeSkPixmapChecksum(pixmap);
checksumCache.insert({ id, checksum }); return checksum;
}
// This does the invert operation, i.e. result = color(255-R,255-G,255-B,A). void setBlenderInvert(SkPaint* paint)
{ if (!invertBlender)
{ // Note that the colors are premultiplied, so '1 - dst.r' must be // written as 'dst.a - dst.r', since premultiplied R is in the range (0-A). constchar* const diff = R"(
vec4 main( vec4 src, vec4 dst )
{ return vec4( dst.a - dst.r, dst.a - dst.g, dst.a - dst.b, dst.a );
}
)"; auto effect = SkRuntimeEffect::MakeForBlender(SkString(diff)); if (!effect.effect)
{
SAL_WARN("vcl.skia", "SKRuntimeEffect::MakeForBlender failed: " << effect.errorText.c_str());
abort();
}
invertBlender = effect.effect->makeBlender(nullptr);
}
paint->setBlender(invertBlender);
}
// This does the xor operation, i.e. bitwise xor of RGB values of both colors. void setBlenderXor(SkPaint* paint)
{ if (!xorBlender)
{ // Note that the colors are premultiplied, converting to 0-255 range // must also unpremultiply. constchar* const diff = R"(
vec4 main( vec4 src, vec4 dst )
{ return vec4( float(int(src.r * src.a * 255.0) ^ int(dst.r * dst.a * 255.0)) / 255.0 / dst.a, float(int(src.g * src.a * 255.0) ^ int(dst.g * dst.a * 255.0)) / 255.0 / dst.a, float(int(src.b * src.a * 255.0) ^ int(dst.b * dst.a * 255.0)) / 255.0 / dst.a,
dst.a );
}
)";
SkRuntimeEffect::Options opts; // Skia does not allow binary operators in the default ES2Strict mode, but that's only // because of OpenGL support. We don't use OpenGL, and it's safe for all modes that we do use. // https://groups.google.com/g/skia-discuss/c/EPLuQbg64Kc/m/2uDXFIGhAwAJ
opts.maxVersionAllowed = SkSL::Version::k300; auto effect = SkRuntimeEffect::MakeForBlender(SkString(diff), opts); if (!effect.effect)
{
SAL_WARN("vcl.skia", "SKRuntimeEffect::MakeForBlender failed: " << effect.errorText.c_str());
abort();
}
xorBlender = effect.effect->makeBlender(nullptr);
}
paint->setBlender(xorBlender);
}
// Skia should not be used from VCL backends that do not actually support it, as there will be setup missing. // The code here (that is in the vcl lib) needs a function for creating Vulkan/Metal context that is // usually available only in the backend libs. void prepareSkia(std::unique_ptr<skwindow::WindowContext> (*createGpuWindowContext)(bool))
{
setCreateGpuWindowContext(createGpuWindowContext);
skiaSupportedByBackend = true;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.