diff options
-rw-r--r-- | external/skia/share-grcontext.patch.1 | 65 | ||||
-rw-r--r-- | vcl/inc/skia/utils.hxx | 4 | ||||
-rw-r--r-- | vcl/inc/skia/x11/gdiimpl.hxx | 5 | ||||
-rw-r--r-- | vcl/skia/SkiaHelper.cxx | 81 | ||||
-rw-r--r-- | vcl/skia/gdiimpl.cxx | 16 | ||||
-rw-r--r-- | vcl/skia/x11/gdiimpl.cxx | 63 |
6 files changed, 167 insertions, 67 deletions
diff --git a/external/skia/share-grcontext.patch.1 b/external/skia/share-grcontext.patch.1 index 96a4fce322e5..a6d815d0024a 100644 --- a/external/skia/share-grcontext.patch.1 +++ b/external/skia/share-grcontext.patch.1 @@ -1,5 +1,5 @@ diff --git a/tools/sk_app/VulkanWindowContext.cpp b/tools/sk_app/VulkanWindowContext.cpp -index c2b26b4254..e8ddbcc886 100644 +index c2b26b4254..fe1afd30a1 100644 --- a/tools/sk_app/VulkanWindowContext.cpp +++ b/tools/sk_app/VulkanWindowContext.cpp @@ -24,8 +24,10 @@ @@ -66,7 +66,7 @@ index c2b26b4254..e8ddbcc886 100644 PFN_vkGetPhysicalDeviceProperties localGetPhysicalDeviceProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties>( -@@ -87,21 +98,31 @@ void VulkanWindowContext::initializeContext() { +@@ -87,21 +98,30 @@ void VulkanWindowContext::initializeContext() { backendContext.fInstance, VK_NULL_HANDLE)); if (!localGetPhysicalDeviceProperties) { @@ -75,9 +75,11 @@ index c2b26b4254..e8ddbcc886 100644 + fGlobalShared.reset(); return; } - VkPhysicalDeviceProperties physDeviceProperties; - localGetPhysicalDeviceProperties(backendContext.fPhysicalDevice, &physDeviceProperties); - uint32_t physDevVersion = physDeviceProperties.apiVersion; +- VkPhysicalDeviceProperties physDeviceProperties; +- localGetPhysicalDeviceProperties(backendContext.fPhysicalDevice, &physDeviceProperties); +- uint32_t physDevVersion = physDeviceProperties.apiVersion; ++ localGetPhysicalDeviceProperties(backendContext.fPhysicalDevice, &d->physDeviceProperties); ++ uint32_t physDevVersion = d->physDeviceProperties.apiVersion; - fInterface.reset(new GrVkInterface(backendContext.fGetProc, fInstance, fDevice, + d->fInterface.reset(new GrVkInterface(backendContext.fGetProc, d->fInstance, d->fDevice, @@ -103,7 +105,7 @@ index c2b26b4254..e8ddbcc886 100644 GET_PROC(DestroySurfaceKHR); GET_PROC(GetPhysicalDeviceSurfaceSupportKHR); GET_PROC(GetPhysicalDeviceSurfaceCapabilitiesKHR); -@@ -109,7 +130,6 @@ void VulkanWindowContext::initializeContext() { +@@ -109,7 +129,6 @@ void VulkanWindowContext::initializeContext() { GET_PROC(GetPhysicalDeviceSurfacePresentModesKHR); GET_DEV_PROC(DeviceWaitIdle); GET_DEV_PROC(QueueWaitIdle); @@ -111,7 +113,7 @@ index c2b26b4254..e8ddbcc886 100644 GET_DEV_PROC(CreateSwapchainKHR); GET_DEV_PROC(DestroySwapchainKHR); GET_DEV_PROC(GetSwapchainImagesKHR); -@@ -117,46 +137,44 @@ void VulkanWindowContext::initializeContext() { +@@ -117,46 +136,44 @@ void VulkanWindowContext::initializeContext() { GET_DEV_PROC(QueuePresentKHR); GET_DEV_PROC(GetDeviceQueue); @@ -168,7 +170,7 @@ index c2b26b4254..e8ddbcc886 100644 nullptr); if (VK_SUCCESS != res) { return false; -@@ -164,14 +182,14 @@ bool VulkanWindowContext::createSwapchain(int width, int height, +@@ -164,14 +181,14 @@ bool VulkanWindowContext::createSwapchain(int width, int height, SkAutoMalloc surfaceFormatAlloc(surfaceFormatCount * sizeof(VkSurfaceFormatKHR)); VkSurfaceFormatKHR* surfaceFormats = (VkSurfaceFormatKHR*)surfaceFormatAlloc.get(); @@ -185,7 +187,7 @@ index c2b26b4254..e8ddbcc886 100644 nullptr); if (VK_SUCCESS != res) { return false; -@@ -179,7 +197,7 @@ bool VulkanWindowContext::createSwapchain(int width, int height, +@@ -179,7 +196,7 @@ bool VulkanWindowContext::createSwapchain(int width, int height, SkAutoMalloc presentModeAlloc(presentModeCount * sizeof(VkPresentModeKHR)); VkPresentModeKHR* presentModes = (VkPresentModeKHR*)presentModeAlloc.get(); @@ -194,7 +196,7 @@ index c2b26b4254..e8ddbcc886 100644 presentModes); if (VK_SUCCESS != res) { return false; -@@ -286,8 +304,8 @@ bool VulkanWindowContext::createSwapchain(int width, int height, +@@ -286,8 +303,8 @@ bool VulkanWindowContext::createSwapchain(int width, int height, swapchainCreateInfo.imageArrayLayers = 1; swapchainCreateInfo.imageUsage = usageFlags; @@ -205,7 +207,7 @@ index c2b26b4254..e8ddbcc886 100644 swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; swapchainCreateInfo.queueFamilyIndexCount = 2; swapchainCreateInfo.pQueueFamilyIndices = queueFamilies; -@@ -303,18 +321,18 @@ bool VulkanWindowContext::createSwapchain(int width, int height, +@@ -303,18 +320,18 @@ bool VulkanWindowContext::createSwapchain(int width, int height, swapchainCreateInfo.clipped = true; swapchainCreateInfo.oldSwapchain = fSwapchain; @@ -227,7 +229,7 @@ index c2b26b4254..e8ddbcc886 100644 } this->createBuffers(swapchainCreateInfo.imageFormat, colorType); -@@ -323,10 +341,10 @@ bool VulkanWindowContext::createSwapchain(int width, int height, +@@ -323,10 +340,10 @@ bool VulkanWindowContext::createSwapchain(int width, int height, } void VulkanWindowContext::createBuffers(VkFormat format, SkColorType colorType) { @@ -240,7 +242,7 @@ index c2b26b4254..e8ddbcc886 100644 // set up initial image layouts and create surfaces fImageLayouts = new VkImageLayout[fImageCount]; -@@ -341,7 +359,7 @@ void VulkanWindowContext::createBuffers(VkFormat format, SkColorType colorType) +@@ -341,7 +358,7 @@ void VulkanWindowContext::createBuffers(VkFormat format, SkColorType colorType) info.fImageTiling = VK_IMAGE_TILING_OPTIMAL; info.fFormat = format; info.fLevelCount = 1; @@ -249,7 +251,7 @@ index c2b26b4254..e8ddbcc886 100644 if (fSampleCount == 1) { GrBackendRenderTarget backendRT(fWidth, fHeight, fSampleCount, info); -@@ -372,8 +390,8 @@ void VulkanWindowContext::createBuffers(VkFormat format, SkColorType colorType) +@@ -372,8 +389,8 @@ void VulkanWindowContext::createBuffers(VkFormat format, SkColorType colorType) fBackbuffers = new BackbufferInfo[fImageCount + 1]; for (uint32_t i = 0; i < fImageCount + 1; ++i) { fBackbuffers[i].fImageIndex = -1; @@ -260,7 +262,7 @@ index c2b26b4254..e8ddbcc886 100644 &fBackbuffers[i].fRenderSemaphore)); SkASSERT(result == VK_SUCCESS); } -@@ -385,8 +403,8 @@ void VulkanWindowContext::destroyBuffers() { +@@ -385,8 +402,8 @@ void VulkanWindowContext::destroyBuffers() { if (fBackbuffers) { for (uint32_t i = 0; i < fImageCount + 1; ++i) { fBackbuffers[i].fImageIndex = -1; @@ -271,7 +273,7 @@ index c2b26b4254..e8ddbcc886 100644 fBackbuffers[i].fRenderSemaphore, nullptr)); } -@@ -411,41 +429,59 @@ VulkanWindowContext::~VulkanWindowContext() { +@@ -411,41 +428,59 @@ VulkanWindowContext::~VulkanWindowContext() { void VulkanWindowContext::destroyContext() { if (this->isValid()) { fQueueWaitIdle(fPresentQueue); @@ -344,7 +346,7 @@ index c2b26b4254..e8ddbcc886 100644 } VulkanWindowContext::BackbufferInfo* VulkanWindowContext::getAvailableBackbuffer() { -@@ -471,35 +507,35 @@ sk_sp<SkSurface> VulkanWindowContext::getBackbufferSurface() { +@@ -471,35 +506,35 @@ sk_sp<SkSurface> VulkanWindowContext::getBackbufferSurface() { semaphoreInfo.pNext = nullptr; semaphoreInfo.flags = 0; VkSemaphore semaphore; @@ -386,7 +388,7 @@ index c2b26b4254..e8ddbcc886 100644 return nullptr; } } -@@ -543,4 +579,6 @@ void VulkanWindowContext::swapBuffers() { +@@ -543,4 +578,6 @@ void VulkanWindowContext::swapBuffers() { fQueuePresentKHR(fPresentQueue, &presentInfo); } @@ -394,10 +396,16 @@ index c2b26b4254..e8ddbcc886 100644 + } //namespace sk_app diff --git a/tools/sk_app/VulkanWindowContext.h b/tools/sk_app/VulkanWindowContext.h -index 2db9e79ae6..11e94aae31 100644 +index 2db9e79ae6..aa48fc2951 100644 --- a/tools/sk_app/VulkanWindowContext.h +++ b/tools/sk_app/VulkanWindowContext.h -@@ -23,14 +23,30 @@ class GrRenderTarget; +@@ -19,18 +19,36 @@ + #include "tools/gpu/vk/VkTestUtils.h" + #include "tools/sk_app/WindowContext.h" + ++#include <cassert> ++ + class GrRenderTarget; namespace sk_app { @@ -430,7 +438,15 @@ index 2db9e79ae6..11e94aae31 100644 void resize(int w, int h) override { this->createSwapchain(w, h, fDisplayParams); -@@ -53,6 +69,7 @@ public: +@@ -50,9 +68,15 @@ public: + VulkanWindowContext(const DisplayParams&, CreateVkSurfaceFn, CanPresentFn, + PFN_vkGetInstanceProcAddr, PFN_vkGetDeviceProcAddr); + ++ static const VkPhysicalDeviceProperties& getPhysDeviceProperties() { ++ assert( fGlobalShared != nullptr ); ++ return fGlobalShared->physDeviceProperties; ++ } ++ private: void initializeContext(); void destroyContext(); @@ -438,7 +454,7 @@ index 2db9e79ae6..11e94aae31 100644 struct BackbufferInfo { uint32_t fImageIndex; // image this is associated with -@@ -64,11 +81,6 @@ private: +@@ -64,11 +88,6 @@ private: void createBuffers(VkFormat format, SkColorType colorType); void destroyBuffers(); @@ -450,7 +466,7 @@ index 2db9e79ae6..11e94aae31 100644 // Create functions CreateVkSurfaceFn fCreateVkSurfaceFn; CanPresentFn fCanPresentFn; -@@ -90,20 +102,41 @@ private: +@@ -90,20 +109,44 @@ private: PFN_vkAcquireNextImageKHR fAcquireNextImageKHR = nullptr; PFN_vkQueuePresentKHR fQueuePresentKHR = nullptr; @@ -481,6 +497,9 @@ index 2db9e79ae6..11e94aae31 100644 + // It should exist as long as the context exists. + VkPhysicalDeviceFeatures2 features; + ++ // Store this to make it accessible. ++ VkPhysicalDeviceProperties physDeviceProperties; ++ uint32_t fGraphicsQueueIndex; VkQueue fGraphicsQueue; uint32_t fPresentQueueIndex; diff --git a/vcl/inc/skia/utils.hxx b/vcl/inc/skia/utils.hxx index 6fd61afd0a05..af81ca99f2dd 100644 --- a/vcl/inc/skia/utils.hxx +++ b/vcl/inc/skia/utils.hxx @@ -42,6 +42,10 @@ inline sk_sp<SkSurface> createSkSurface(const Size& size, SkColorType type = kN3 return createSkSurface(size.Width(), size.Height(), type); } +/// This function is in the X11/Win backend libs, but needs to be used in SkiaHelper in the vcl lib. +VCL_DLLPUBLIC void + setCreateVulkanWindowContext(std::unique_ptr<sk_app::WindowContext> (*function)()); + #ifdef DBG_UTIL void prefillSurface(sk_sp<SkSurface>& surface); VCL_DLLPUBLIC void dump(const SkBitmap& bitmap, const char* file); diff --git a/vcl/inc/skia/x11/gdiimpl.hxx b/vcl/inc/skia/x11/gdiimpl.hxx index ad131f64c973..8cedf23d3e1b 100644 --- a/vcl/inc/skia/x11/gdiimpl.hxx +++ b/vcl/inc/skia/x11/gdiimpl.hxx @@ -15,6 +15,7 @@ #include <unx/salgdi.h> #include <unx/x11/x11gdiimpl.h> #include <skia/gdiimpl.hxx> +#include <skia/utils.hxx> class VCL_PLUGIN_PUBLIC X11SkiaSalGraphicsImpl final : public SkiaSalGraphicsImpl, public X11GraphicsImpl @@ -34,6 +35,10 @@ private: virtual void createWindowContext() override; virtual void performFlush() override; virtual bool avoidRecreateByResize() const override; + static std::unique_ptr<sk_app::WindowContext> + createWindowContext(Display* display, Drawable drawable, const SalVisual* visual, int width, + int height, SkiaHelper::RenderMethod renderMethod); + friend std::unique_ptr<sk_app::WindowContext> createVulkanWindowContext(); }; #endif // INCLUDED_VCL_INC_SKIA_X11_GDIIMPL_HXX diff --git a/vcl/skia/SkiaHelper.cxx b/vcl/skia/SkiaHelper.cxx index 281fdaf65f77..00eafbaf54c4 100644 --- a/vcl/skia/SkiaHelper.cxx +++ b/vcl/skia/SkiaHelper.cxx @@ -14,6 +14,7 @@ #include <officecfg/Office/Common.hxx> #include <watchdog.hxx> #include <skia/zone.hxx> +#include <sal/log.hxx> #if !HAVE_FEATURE_SKIA @@ -28,7 +29,6 @@ bool isVCLSkiaEnabled() { return false; } #include <skia/utils.hxx> #include <SkSurface.h> -#include <tools/sk_app/VulkanWindowContext.h> #ifdef DBG_UTIL #include <fstream> @@ -36,14 +36,55 @@ bool isVCLSkiaEnabled() { return false; } namespace SkiaHelper { -static bool supportsVCLSkia() +static bool isVulkanBlacklisted(const VkPhysicalDeviceProperties& props) { - static bool bDisableSkia = !!getenv("SAL_DISABLESKIA"); - bool bBlacklisted = false; // TODO isDeviceBlacklisted(); + static const char* const types[] + = { "other", "integrated", "discrete", "virtual", "cpu", "??" }; // VkPhysicalDeviceType + SAL_INFO("vcl.skia", + "Vulkan API version: " + << (props.apiVersion >> 22) << "." << ((props.apiVersion >> 12) & 0x3ff) << "." + << (props.apiVersion & 0xfff) << ", driver version: " << std::hex + << props.driverVersion << ", vendor:" << props.vendorID + << ", device: " << props.deviceID << std::dec + << ", type: " << types[std::min<unsigned>(props.deviceType, SAL_N_ELEMENTS(types))] + << ", name: " << props.deviceName); + return false; +} - return !bDisableSkia && !bBlacklisted; +static void checkDeviceBlacklisted() +{ + static bool done = false; + if (!done) + { + SkiaZone zone; + + switch (renderMethodToUse()) + { + case RenderVulkan: + { + GrContext* grContext = SkiaHelper::getSharedGrContext(); + bool blacklisted = true; // assume the worst + if (grContext) // Vulkan was initialized properly + { + blacklisted = isVulkanBlacklisted( + sk_app::VulkanWindowContext::getPhysDeviceProperties()); + SAL_INFO("vcl.skia", "Vulkan blacklisted: " << blacklisted); + } + else + SAL_INFO("vcl.skia", "Vulkan could not be initialized"); + if (blacklisted) + disableRenderMethod(RenderVulkan); + break; + } + case RenderRaster: + return; // software, never blacklisted + } + done = true; + } } +static bool supportsVCLSkia() { return !getenv("SAL_DISABLESKIA"); } + bool isVCLSkiaEnabled() { /** @@ -93,6 +134,9 @@ bool isVCLSkiaEnabled() if (Application::IsSafeModeEnabled()) bEnable = false; + if (bEnable) + checkDeviceBlacklisted(); // switch to raster if driver is blacklisted + bRet = bEnable; } @@ -138,6 +182,12 @@ void disableRenderMethod(RenderMethod method) static sk_app::VulkanWindowContext::SharedGrContext* sharedGrContext; +static std::unique_ptr<sk_app::WindowContext> (*createVulkanWindowContextFunction)() = nullptr; +void setCreateVulkanWindowContext(std::unique_ptr<sk_app::WindowContext> (*function)()) +{ + createVulkanWindowContextFunction = function; +} + GrContext* getSharedGrContext() { SkiaZone zone; @@ -145,7 +195,7 @@ GrContext* getSharedGrContext() if (sharedGrContext) return sharedGrContext->getGrContext(); // TODO mutex? - // Setup the shared GrContext from Skia's (patched) VulkanWindowContext, if it's been + // Set up the shared GrContext from Skia's (patched) VulkanWindowContext, if it's been // already set up. sk_app::VulkanWindowContext::SharedGrContext context = sk_app::VulkanWindowContext::getSharedGrContext(); @@ -155,9 +205,22 @@ GrContext* getSharedGrContext() sharedGrContext = new sk_app::VulkanWindowContext::SharedGrContext(context); return grContext; } - // TODO - // SkiaSalGraphicsImpl::createOffscreenSurface() creates the shared context using a dummy window, - // but we do not have a window here. Is it worth it to try to do it here? + static bool done = false; + if (done) + return nullptr; + done = true; + if (!createVulkanWindowContextFunction) + return nullptr; + std::unique_ptr<sk_app::WindowContext> tmpContext = createVulkanWindowContextFunction(); + // Set up using the shared context created by the call above, if successful. + context = sk_app::VulkanWindowContext::getSharedGrContext(); + grContext = context.getGrContext(); + if (grContext) + { + sharedGrContext = new sk_app::VulkanWindowContext::SharedGrContext(context); + return grContext; + } + disableRenderMethod(RenderVulkan); return nullptr; } diff --git a/vcl/skia/gdiimpl.cxx b/vcl/skia/gdiimpl.cxx index b2d4a050f796..dd7431a2662b 100644 --- a/vcl/skia/gdiimpl.cxx +++ b/vcl/skia/gdiimpl.cxx @@ -252,21 +252,7 @@ void SkiaSalGraphicsImpl::createOffscreenSurface() { case SkiaHelper::RenderVulkan: { - GrContext* grContext = SkiaHelper::getSharedGrContext(); - // We may not get a GrContext if called before any onscreen window is created. - if (!grContext) - { - SAL_INFO("vcl.skia", - "creating Vulkan offscreen GPU surface before any window exists"); - // Create temporary WindowContext with no window. That will fail, - // but it will initialize the shared GrContext. - createWindowContext(); - // This will use the temporarily created context. - grContext = SkiaHelper::getSharedGrContext(); - // Destroy the temporary WindowContext. - destroySurface(); - } - if (grContext) + if (SkiaHelper::getSharedGrContext()) { mSurface = SkiaHelper::createSkSurface(GetWidth(), GetHeight()); assert(mSurface); diff --git a/vcl/skia/x11/gdiimpl.cxx b/vcl/skia/x11/gdiimpl.cxx index 503eeee3fa32..c587b143ef69 100644 --- a/vcl/skia/x11/gdiimpl.cxx +++ b/vcl/skia/x11/gdiimpl.cxx @@ -19,9 +19,8 @@ #include <skia/x11/gdiimpl.hxx> #include <tools/sk_app/unix/WindowContextFactory_unix.h> -#include <tools/sk_app/WindowContext.h> -#include <vcl/skia/SkiaHelper.hxx> +#include <skia/utils.hxx> #include <skia/zone.hxx> X11SkiaSalGraphicsImpl::X11SkiaSalGraphicsImpl(X11SalGraphics& rParent) @@ -41,18 +40,32 @@ void X11SkiaSalGraphicsImpl::Init() void X11SkiaSalGraphicsImpl::createWindowContext() { + assert(mX11Parent.GetDrawable() != None); + mWindowContext = createWindowContext(mX11Parent.GetXDisplay(), mX11Parent.GetDrawable(), + &mX11Parent.GetVisual(), GetWidth(), GetHeight(), + SkiaHelper::renderMethodToUse()); + if (mWindowContext && SkiaHelper::renderMethodToUse() == SkiaHelper::RenderVulkan) + mIsGPU = true; + else + mIsGPU = false; +} + +std::unique_ptr<sk_app::WindowContext> +X11SkiaSalGraphicsImpl::createWindowContext(Display* display, Drawable drawable, + const SalVisual* visual, int width, int height, + SkiaHelper::RenderMethod renderMethod) +{ SkiaZone zone; sk_app::DisplayParams displayParams; displayParams.fColorType = kN32_SkColorType; sk_app::window_context_factory::XlibWindowInfo winInfo; - winInfo.fDisplay = mX11Parent.GetXDisplay(); - winInfo.fWindow = mX11Parent.GetDrawable(); - assert(winInfo.fDisplay); - // Allow window being None if offscreen, this is used to temporarily create GrContext - // for an offscreen surface. - assert(winInfo.fWindow != None || isOffscreen()); + assert(display); + winInfo.fDisplay = display; + winInfo.fWindow = drawable; winInfo.fFBConfig = nullptr; // not used - winInfo.fVisualInfo = const_cast<SalVisual*>(&mX11Parent.GetVisual()); + winInfo.fVisualInfo = const_cast<SalVisual*>(visual); + winInfo.fWidth = width; + winInfo.fHeight = height; #ifdef DBG_UTIL // Our patched Skia has VulkanWindowContext that shares GrContext, which requires // that the X11 visual is always the same. Ensure it is so. @@ -60,25 +73,18 @@ void X11SkiaSalGraphicsImpl::createWindowContext() assert(checkVisualID == -1U || winInfo.fVisualInfo->visualid == checkVisualID); checkVisualID = winInfo.fVisualInfo->visualid; #endif - winInfo.fWidth = GetWidth(); - winInfo.fHeight = GetHeight(); - switch (SkiaHelper::renderMethodToUse()) + switch (renderMethod) { case SkiaHelper::RenderRaster: // TODO The Skia Xlib code actually requires the non-native color type to work properly. displayParams.fColorType = (displayParams.fColorType == kBGRA_8888_SkColorType ? kRGBA_8888_SkColorType : kBGRA_8888_SkColorType); - mWindowContext - = sk_app::window_context_factory::MakeRasterForXlib(winInfo, displayParams); - mIsGPU = false; - break; + return sk_app::window_context_factory::MakeRasterForXlib(winInfo, displayParams); case SkiaHelper::RenderVulkan: - mWindowContext - = sk_app::window_context_factory::MakeVulkanForXlib(winInfo, displayParams); - mIsGPU = true; - break; + return sk_app::window_context_factory::MakeVulkanForXlib(winInfo, displayParams); } + abort(); } bool X11SkiaSalGraphicsImpl::avoidRecreateByResize() const @@ -116,4 +122,21 @@ void X11SkiaSalGraphicsImpl::performFlush() mWindowContext->swapBuffers(); } +std::unique_ptr<sk_app::WindowContext> createVulkanWindowContext() +{ + SalDisplay* salDisplay = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + return X11SkiaSalGraphicsImpl::createWindowContext( + salDisplay->GetDisplay(), None, &salDisplay->GetVisual(salDisplay->GetDefaultXScreen()), 1, + 1, SkiaHelper::RenderVulkan); +} + +namespace +{ +struct SetFunction +{ + SetFunction() { SkiaHelper::setCreateVulkanWindowContext(createVulkanWindowContext); } +}; +SetFunction setFunction; +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |